diff --git a/src/hotspot/share/services/mallocTracker.cpp b/src/hotspot/share/services/mallocTracker.cpp
index 285a60d853f..e27d312ad81 100644
--- a/src/hotspot/share/services/mallocTracker.cpp
+++ b/src/hotspot/share/services/mallocTracker.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2022, 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
@@ -31,6 +31,8 @@
 #include "utilities/debug.hpp"
 #include "utilities/ostream.hpp"
 
+#include "jvm_io.h"
+
 size_t MallocMemorySummary::_snapshot[CALC_OBJ_SIZE_IN_TYPE(MallocMemorySnapshot, size_t)];
 
 #ifdef ASSERT
@@ -115,7 +117,7 @@ void MallocHeader::mark_block_as_dead() {
 void MallocHeader::release() {
   assert(MemTracker::enabled(), "Sanity");
 
-  check_block_integrity();
+  assert_block_integrity();
 
   MallocMemorySummary::record_free(size(), flags());
   MallocMemorySummary::record_free_malloc_header(sizeof(MallocHeader));
@@ -153,13 +155,18 @@ void MallocHeader::print_block_on_error(outputStream* st, address bad_address) c
     os::print_hex_dump(st, from1, to2, 1);
   }
 }
+void MallocHeader::assert_block_integrity() const {
+  char msg[256];
+  address corruption = NULL;
+  if (!check_block_integrity(msg, sizeof(msg), &corruption)) {
+    if (corruption != NULL) {
+      print_block_on_error(tty, (address)this);
+    }
+    fatal("NMT corruption: Block at " PTR_FORMAT ": %s", p2i(this), msg);
+  }
+}
 
-// Check block integrity. If block is broken, print out a report
-// to tty (optionally with hex dump surrounding the broken block),
-// then trigger a fatal error.
-void MallocHeader::check_block_integrity() const {
-
-#define PREFIX "NMT corruption: "
+bool MallocHeader::check_block_integrity(char* msg, size_t msglen, address* p_corruption) const {
   // Note: if you modify the error messages here, make sure you
   // adapt the associated gtests too.
 
@@ -167,7 +174,8 @@ void MallocHeader::check_block_integrity() const {
   // values. Note that we should not call this for ::free(NULL),
   // which should be handled by os::free() above us.
   if (((size_t)p2i(this)) < K) {
-    fatal(PREFIX "Block at " PTR_FORMAT ": invalid block address", p2i(this));
+    jio_snprintf(msg, msglen, "invalid block address");
+    return false;
   }
 
   // From here on we assume the block pointer to be valid. We could
@@ -186,37 +194,42 @@ void MallocHeader::check_block_integrity() const {
   // Should we ever start using std::max_align_t, this would be one place to
   // fix up.
   if (!is_aligned(this, sizeof(uint64_t))) {
-    print_block_on_error(tty, (address)this);
-    fatal(PREFIX "Block at " PTR_FORMAT ": block address is unaligned", p2i(this));
+    *p_corruption = (address)this;
+    jio_snprintf(msg, msglen, "block address is unaligned");
+    return false;
   }
 
   // Check header canary
   if (_canary != _header_canary_life_mark) {
-    print_block_on_error(tty, (address)this);
-    fatal(PREFIX "Block at " PTR_FORMAT ": header canary broken.", p2i(this));
+    *p_corruption = (address)this;
+    jio_snprintf(msg, msglen, "header canary broken");
+    return false;
   }
 
 #ifndef _LP64
   // On 32-bit we have a second canary, check that one too.
   if (_alt_canary != _header_alt_canary_life_mark) {
-    print_block_on_error(tty, (address)this);
-    fatal(PREFIX "Block at " PTR_FORMAT ": header alternate canary broken.", p2i(this));
+    *p_corruption = (address)this;
+    jio_snprintf(msg, msglen, "header canary broken");
+    return false;
   }
 #endif
 
   // Does block size seems reasonable?
   if (_size >= max_reasonable_malloc_size) {
-    print_block_on_error(tty, (address)this);
-    fatal(PREFIX "Block at " PTR_FORMAT ": header looks invalid (weirdly large block size)", p2i(this));
+    *p_corruption = (address)this;
+    jio_snprintf(msg, msglen, "header looks invalid (weirdly large block size)");
+    return false;
   }
 
   // Check footer canary
   if (get_footer() != _footer_canary_life_mark) {
-    print_block_on_error(tty, footer_address());
-    fatal(PREFIX "Block at " PTR_FORMAT ": footer canary broken at " PTR_FORMAT " (buffer overflow?)",
-          p2i(this), p2i(footer_address()));
+    *p_corruption = footer_address();
+    jio_snprintf(msg, msglen, "footer canary broken at " PTR_FORMAT " (buffer overflow?)",
+                p2i(footer_address()));
+    return false;
   }
-#undef PREFIX
+  return true;
 }
 
 bool MallocHeader::record_malloc_site(const NativeCallStack& stack, size_t size,
diff --git a/src/hotspot/share/services/mallocTracker.hpp b/src/hotspot/share/services/mallocTracker.hpp
index 6490b3d53ff..a0953c2b186 100644
--- a/src/hotspot/share/services/mallocTracker.hpp
+++ b/src/hotspot/share/services/mallocTracker.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2022, 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
@@ -314,10 +314,9 @@ class MallocHeader {
   // We discount sizes larger than these
   static const size_t max_reasonable_malloc_size = LP64_ONLY(256 * G) NOT_LP64(3500 * M);
 
-  // Check block integrity. If block is broken, print out a report
-  // to tty (optionally with hex dump surrounding the broken block),
-  // then trigger a fatal error.
-  void check_block_integrity() const;
+  // If block is broken, print out a report to tty (optionally with
+  // hex dump surrounding the broken block), then trigger a fatal error
+  void assert_block_integrity() const;
   void print_block_on_error(outputStream* st, address bad_address) const;
   void mark_block_as_dead();
 
@@ -363,6 +362,11 @@ class MallocHeader {
   // Cleanup tracking information and mark block as dead before the memory is released.
   void release();
 
+  // If block is broken, fill in a short descriptive text in out,
+  // an option pointer to the corruption in p_corruption, and return false.
+  // Return true if block is fine.
+  bool check_block_integrity(char* msg, size_t msglen, address* p_corruption) const;
+
  private:
   inline void set_size(size_t size) {
     _size = size;
diff --git a/src/hotspot/share/services/virtualMemoryTracker.cpp b/src/hotspot/share/services/virtualMemoryTracker.cpp
index 77fb24927f5..53a2cf10642 100644
--- a/src/hotspot/share/services/virtualMemoryTracker.cpp
+++ b/src/hotspot/share/services/virtualMemoryTracker.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2022, 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
@@ -671,3 +671,29 @@ bool VirtualMemoryTracker::walk_virtual_memory(VirtualMemoryWalker* walker) {
    }
   return true;
 }
+
+class FindAndSnapshotRegionWalker : public VirtualMemoryWalker {
+private:
+  ReservedMemoryRegion& _region;
+  const address         _p;
+  bool                  _found_region;
+public:
+  FindAndSnapshotRegionWalker(void* p, ReservedMemoryRegion& region) :
+    _region(region), _p((address)p), _found_region(false) { }
+
+  bool do_allocation_site(const ReservedMemoryRegion* rgn) {
+    if (rgn->contain_address(_p)) {
+      _region = *rgn;
+      _found_region = true;
+      return false;
+    }
+    return true;
+  }
+  bool found_region() const { return _found_region; }
+};
+
+const bool VirtualMemoryTracker::snapshot_region_contains(void* p, ReservedMemoryRegion& region) {
+  FindAndSnapshotRegionWalker walker(p, region);
+  walk_virtual_memory(&walker);
+  return walker.found_region();
+}
diff --git a/src/hotspot/share/services/virtualMemoryTracker.hpp b/src/hotspot/share/services/virtualMemoryTracker.hpp
index c96af6b8387..2f04503cf6e 100644
--- a/src/hotspot/share/services/virtualMemoryTracker.hpp
+++ b/src/hotspot/share/services/virtualMemoryTracker.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2022, 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
@@ -341,7 +341,7 @@ class ReservedMemoryRegion : public VirtualMemoryRegion {
     return *this;
   }
 
-  const char* flag_name() { return NMTUtil::flag_to_name(_flag); }
+  const char* flag_name() const { return NMTUtil::flag_to_name(_flag); }
 
  private:
   // The committed region contains the uncommitted region, subtract the uncommitted
@@ -387,6 +387,8 @@ class VirtualMemoryTracker : AllStatic {
   // Walk virtual memory data structure for creating baseline, etc.
   static bool walk_virtual_memory(VirtualMemoryWalker* walker);
 
+  static const bool snapshot_region_contains(void* p, ReservedMemoryRegion& region);
+
   // Snapshot current thread stacks
   static void snapshot_thread_stacks();
 
diff --git a/src/hotspot/share/utilities/debug.cpp b/src/hotspot/share/utilities/debug.cpp
index 1a0c3a84647..c3c98d3067f 100644
--- a/src/hotspot/share/utilities/debug.cpp
+++ b/src/hotspot/share/utilities/debug.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2022, 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
@@ -44,6 +44,7 @@
 #include "runtime/handles.inline.hpp"
 #include "runtime/java.hpp"
 #include "runtime/os.hpp"
+#include "runtime/safefetch.inline.hpp"
 #include "runtime/sharedRuntime.hpp"
 #include "runtime/stubCodeGenerator.hpp"
 #include "runtime/stubRoutines.hpp"
@@ -51,7 +52,9 @@
 #include "runtime/vframe.hpp"
 #include "runtime/vm_version.hpp"
 #include "services/heapDumper.hpp"
+#include "services/mallocTracker.hpp"
 #include "services/memTracker.hpp"
+#include "services/virtualMemoryTracker.hpp"
 #include "utilities/defaultStream.hpp"
 #include "utilities/events.hpp"
 #include "utilities/formatBuffer.hpp"
@@ -480,6 +483,41 @@ extern "C" JNIEXPORT void pp(void* p) {
     oop obj = cast_to_oop(p);
     obj->print();
   } else {
+#if INCLUDE_NMT
+    // With NMT
+    if (MemTracker::enabled()) {
+      const NMT_TrackingLevel tracking_level = MemTracker::tracking_level();
+      ReservedMemoryRegion region(0, 0);
+      // Check and snapshot a mmap'd region that contains the pointer
+      if (VirtualMemoryTracker::snapshot_region_contains(p, region)) {
+        tty->print_cr(PTR_FORMAT " in mmap'd memory region [" PTR_FORMAT " - " PTR_FORMAT "] by %s",
+          p2i(p), p2i(region.base()), p2i(region.base() + region.size()), region.flag_name());
+        if (tracking_level == NMT_detail) {
+          region.call_stack()->print_on(tty);
+          tty->cr();
+        }
+        return;
+      }
+      // Check if it is a malloc'd memory block
+      if (CanUseSafeFetchN() && SafeFetchN((intptr_t*)p, 0) != 0) {
+        const MallocHeader* mhdr = (const MallocHeader*)MallocTracker::get_base(p, tracking_level);
+        char msg[256];
+        address p_corrupted;
+        if (SafeFetchN((intptr_t*)mhdr, 0) != 0 && mhdr->check_block_integrity(msg, sizeof(msg), &p_corrupted)) {
+          tty->print_cr(PTR_FORMAT " malloc'd " SIZE_FORMAT " bytes by %s",
+            p2i(p), mhdr->size(), NMTUtil::flag_to_name(mhdr->flags()));
+          if (tracking_level == NMT_detail) {
+            NativeCallStack ncs;
+            if (mhdr->get_stack(ncs)) {
+              ncs.print_on(tty);
+              tty->cr();
+            }
+          }
+          return;
+        }
+      }
+    }
+#endif // INCLUDE_NMT
     tty->print(PTR_FORMAT, p2i(p));
   }
 }