diff --git a/src/hotspot/share/gc/z/zPhysicalMemory.cpp b/src/hotspot/share/gc/z/zPhysicalMemory.cpp
index 0ad7981c60263..66ebe90469c86 100644
--- a/src/hotspot/share/gc/z/zPhysicalMemory.cpp
+++ b/src/hotspot/share/gc/z/zPhysicalMemory.cpp
@@ -276,6 +276,13 @@ void ZPhysicalMemoryManager::try_enable_uncommit(size_t min_capacity, size_t max
 }
 
 void ZPhysicalMemoryManager::nmt_commit(zoffset offset, size_t size) const {
+  // NMT expects a 1-to-1 mapping between virtual and physical memory.
+  // ZGC can temporarily have multiple virtual addresses pointing to
+  // the same physical memory.
+  //
+  // When this function is called we don't know where in the virtual memory
+  // this physical memory will be mapped. So we fake that the virtual memory
+  // address is the heap base + the given offset.
   const zaddress addr = ZOffset::address(offset);
   MemTracker::record_virtual_memory_commit((void*)untype(addr), size, CALLER_PC);
 }
@@ -320,6 +327,11 @@ bool ZPhysicalMemoryManager::commit(ZPhysicalMemory& pmem) {
 
     // Commit segment
     const size_t committed = _backing.commit(segment.start(), segment.size());
+
+    // Register with NMT
+    nmt_commit(segment.start(), committed);
+
+    // Register committed segment
     if (!pmem.commit_segment(i, committed)) {
       // Failed or partially failed
       return false;
@@ -341,6 +353,11 @@ bool ZPhysicalMemoryManager::uncommit(ZPhysicalMemory& pmem) {
 
     // Uncommit segment
     const size_t uncommitted = _backing.uncommit(segment.start(), segment.size());
+
+    // Unregister with NMT
+    nmt_uncommit(segment.start(), uncommitted);
+
+    // Deregister uncommitted segment
     if (!pmem.uncommit_segment(i, uncommitted)) {
       // Failed or partially failed
       return false;
@@ -351,12 +368,16 @@ bool ZPhysicalMemoryManager::uncommit(ZPhysicalMemory& pmem) {
   return true;
 }
 
-void ZPhysicalMemoryManager::pretouch_view(zaddress addr, size_t size) const {
+void ZPhysicalMemoryManager::pretouch(zoffset offset, size_t size) const {
+  const uintptr_t addr = untype(ZOffset::address(offset));
   const size_t page_size = ZLargePages::is_explicit() ? ZGranuleSize : os::vm_page_size();
-  os::pretouch_memory((void*)untype(addr), (void*)(untype(addr) + size), page_size);
+  os::pretouch_memory((void*)addr, (void*)(addr + size), page_size);
 }
 
-void ZPhysicalMemoryManager::map_view(zaddress_unsafe addr, const ZPhysicalMemory& pmem) const {
+// Map virtual memory to physcial memory
+void ZPhysicalMemoryManager::map(zoffset offset, const ZPhysicalMemory& pmem) const {
+  const zaddress_unsafe addr = ZOffset::address_unsafe(offset);
+
   size_t size = 0;
 
   // Map segments
@@ -375,27 +396,9 @@ void ZPhysicalMemoryManager::map_view(zaddress_unsafe addr, const ZPhysicalMemor
   }
 }
 
-void ZPhysicalMemoryManager::unmap_view(zaddress_unsafe addr, size_t size) const {
-  _backing.unmap(addr, size);
-}
-
-void ZPhysicalMemoryManager::pretouch(zoffset offset, size_t size) const {
-  // Pre-touch all views
-  pretouch_view(ZOffset::address(offset), size);
-}
-
-void ZPhysicalMemoryManager::map(zoffset offset, const ZPhysicalMemory& pmem) const {
-  const size_t size = pmem.size();
-
-  // Map all views
-  map_view(ZOffset::address_unsafe(offset), pmem);
-
-  nmt_commit(offset, size);
-}
-
+// Unmap virtual memory from physical memory
 void ZPhysicalMemoryManager::unmap(zoffset offset, size_t size) const {
-  nmt_uncommit(offset, size);
+  const zaddress_unsafe addr = ZOffset::address_unsafe(offset);
 
-  // Unmap all views
-  unmap_view(ZOffset::address_unsafe(offset), size);
+  _backing.unmap(addr, size);
 }
diff --git a/test/hotspot/jtreg/runtime/NMT/NMTJavaHeapTest.java b/test/hotspot/jtreg/runtime/NMT/NMTJavaHeapTest.java
new file mode 100644
index 0000000000000..603760379de48
--- /dev/null
+++ b/test/hotspot/jtreg/runtime/NMT/NMTJavaHeapTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8306841
+ * @summary Sanity check Java Heap size values
+ * @modules java.base/jdk.internal.misc
+ * @library /test/lib
+ * @run driver NMTJavaHeapTest
+ */
+
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.Asserts;
+import jdk.test.lib.Utils;
+import jdk.test.lib.process.OutputAnalyzer;
+
+public class NMTJavaHeapTest {
+    public static void main(String args[]) throws Exception {
+        ProcessBuilder pb = ProcessTools.createTestJvm(
+              "-XX:+UnlockDiagnosticVMOptions",
+              "-XX:+PrintNMTStatistics",
+              "-XX:NativeMemoryTracking=summary",
+              "-version");
+
+        OutputAnalyzer output = new OutputAnalyzer(pb.start());
+
+        // Java Heap (reserved=786432KB, committed=49152KB)
+        String pattern = ".*Java Heap \\(reserved=.*, committed=(.*)\\).*";
+        String committed = output.firstMatch(pattern, 1);
+        Asserts.assertNotNull(committed, "Couldn't find pattern '" + pattern
+                + "': in output '" + output.getOutput() + "'");
+
+        long committedBytes = committedStringToBytes(committed);
+
+        // Must be more than zero
+        Asserts.assertGT(committedBytes, 0L);
+
+        // Compare against the max heap size
+        long maxBytes = Runtime.getRuntime().maxMemory();
+        Asserts.assertLTE(committedBytes, maxBytes);
+    }
+
+    private static long K = 1024;
+    private static long M = K * 1024;
+    private static long G = M * 1024;
+
+    private static long committedStringToBytes(String committed) {
+        long multiplier = 1;
+        if (committed.endsWith("GB")) {
+            multiplier = G;
+            committed = committed.replace("GB", "");
+        } else if (committed.endsWith("MB")) {
+            multiplier = M;
+            committed = committed.replace("MB", "");
+        } else if (committed.endsWith("KB")) {
+            multiplier = K;
+            committed = committed.replace("KB", "");
+        }
+
+        return Long.parseLong(committed) * multiplier;
+    }
+}