diff --git a/memory/build/replace_malloc_bridge.h b/memory/build/replace_malloc_bridge.h
index fb45355d369389d6dbf98687c66def23b285ba91..20683c85df052258b27d9c73a7dc4f045e73434f 100644
--- a/memory/build/replace_malloc_bridge.h
+++ b/memory/build/replace_malloc_bridge.h
@@ -117,6 +117,16 @@ struct DMDFuncs;
 
 namespace phc {
 class AddrInfo;
+
+struct MemoryUsage {
+  // The amount of memory used for PHC metadata, eg information about each
+  // allocation including stacks.
+  size_t mMetadataBytes = 0;
+
+  // The amount of memory lost due to rounding allocation sizes up to the
+  // nearest page.  AKA internal fragmentation.
+  size_t mFragmentationBytes = 0;
+};
 }  // namespace phc
 
 // Callbacks to register debug file handles for Poison IO interpose.
@@ -126,11 +136,10 @@ struct DebugFdRegistry {
 
   virtual void UnRegisterHandle(intptr_t aFd);
 };
-
 }  // namespace mozilla
 
 struct ReplaceMallocBridge {
-  ReplaceMallocBridge() : mVersion(4) {}
+  ReplaceMallocBridge() : mVersion(5) {}
 
   // This method was added in version 1 of the bridge.
   virtual mozilla::dmd::DMDFuncs* GetDMDFuncs() { return nullptr; }
@@ -182,6 +191,10 @@ struct ReplaceMallocBridge {
   // This method was added in version 4 of the bridge.
   virtual bool IsPHCEnabledOnCurrentThread() { return false; }
 
+  // Return PHC memory usage information by filling in the supplied structure.
+  // This method was added in version 5 of the bridge.
+  virtual void PHCMemoryUsage(mozilla::phc::MemoryUsage& aMemoryUsage) {}
+
 #  ifndef REPLACE_MALLOC_IMPL
   // Returns the replace-malloc bridge if its version is at least the
   // requested one.
@@ -249,6 +262,13 @@ struct ReplaceMalloc {
     auto singleton = ReplaceMallocBridge::Get(/* minimumVersion */ 4);
     return singleton ? singleton->IsPHCEnabledOnCurrentThread() : false;
   }
+
+  static void PHCMemoryUsage(mozilla::phc::MemoryUsage& aMemoryUsage) {
+    auto singleton = ReplaceMallocBridge::Get(/* minimumVersion */ 5);
+    if (singleton) {
+      singleton->PHCMemoryUsage(aMemoryUsage);
+    }
+  }
 };
 #  endif
 
diff --git a/memory/replace/phc/PHC.cpp b/memory/replace/phc/PHC.cpp
index 02bc0d3d398caede5a959c66bb7a33965b507f6f..cd0f6322bb98ab117659f9330c4e4524e2517dfd 100644
--- a/memory/replace/phc/PHC.cpp
+++ b/memory/replace/phc/PHC.cpp
@@ -656,6 +656,12 @@ class GMut {
                                 (kPageSize - 1));
     }
 
+    // The internal fragmentation for this allocation.
+    size_t FragmentationBytes() const {
+      MOZ_ASSERT(kPageSize >= UsableSize());
+      return mState == AllocPageState::InUse ? kPageSize - UsableSize() : 0;
+    }
+
     // The allocation stack.
     // - NeverAllocated: Nothing.
     // - InUse | Freed: Some.
@@ -719,6 +725,15 @@ class GMut {
     return page.UsableSize();
   }
 
+  // The total fragmentation in PHC
+  size_t FragmentationBytes() const {
+    size_t sum = 0;
+    for (auto page : mAllocPages) {
+      sum += page.FragmentationBytes();
+    }
+    return sum;
+  }
+
   void SetPageInUse(GMutLock aLock, uintptr_t aIndex,
                     const Maybe<arena_id_t>& aArenaId, uint8_t* aBaseAddr,
                     const StackTrace& aAllocStack) {
@@ -1389,6 +1404,11 @@ static size_t replace_malloc_usable_size(usable_ptr_t aPtr) {
   return gMut->PageUsableSize(lock, index);
 }
 
+static size_t metadata_size() {
+  return sMallocTable.malloc_usable_size(gConst) +
+         sMallocTable.malloc_usable_size(gMut);
+}
+
 void replace_jemalloc_stats(jemalloc_stats_t* aStats,
                             jemalloc_bin_stats_t* aBinStats) {
   sMallocTable.jemalloc_stats_internal(aStats, aBinStats);
@@ -1419,10 +1439,11 @@ void replace_jemalloc_stats(jemalloc_stats_t* aStats,
   // aStats.page_cache and aStats.bin_unused are left unchanged because PHC
   // doesn't have anything corresponding to those.
 
-  // gConst and gMut are normal heap allocations, so they're measured by
+  // The metadata is stored in normal heap allocations, so they're measured by
   // mozjemalloc as `allocated`. Move them into `bookkeeping`.
-  size_t bookkeeping = sMallocTable.malloc_usable_size(gConst) +
-                       sMallocTable.malloc_usable_size(gMut);
+  // They're also reported under explicit/heap-overhead/phc/fragmentation in
+  // about:memory.
+  size_t bookkeeping = metadata_size();
   aStats->allocated -= bookkeeping;
   aStats->bookkeeping += bookkeeping;
 }
@@ -1554,6 +1575,17 @@ class PHCBridge : public ReplaceMallocBridge {
     LOG("IsPHCEnabledOnCurrentThread: %zu\n", size_t(enabled));
     return enabled;
   }
+
+  virtual void PHCMemoryUsage(
+      mozilla::phc::MemoryUsage& aMemoryUsage) override {
+    aMemoryUsage.mMetadataBytes = metadata_size();
+    if (gMut) {
+      MutexAutoLock lock(GMut::sMutex);
+      aMemoryUsage.mFragmentationBytes = gMut->FragmentationBytes();
+    } else {
+      aMemoryUsage.mFragmentationBytes = 0;
+    }
+  }
 };
 
 // WARNING: this function runs *very* early -- before all static initializers
diff --git a/xpcom/base/moz.build b/xpcom/base/moz.build
index 5576b3194c3ca6ccc90016043944fbb305437adb..f685758b6fadb8c99e842c32f0af43f92cf3c638 100644
--- a/xpcom/base/moz.build
+++ b/xpcom/base/moz.build
@@ -220,6 +220,9 @@ if CONFIG["OS_TARGET"] == "Linux":
         "AvailableMemoryWatcherUtils.h",
     ]
 
+if CONFIG["MOZ_PHC"]:
+    DEFINES["MOZ_PHC"] = True
+
 GeneratedFile("ErrorList.h", script="ErrorList.py", entry_point="error_list_h")
 GeneratedFile(
     "ErrorNamesInternal.h", script="ErrorList.py", entry_point="error_names_internal_h"
diff --git a/xpcom/base/nsMemoryReporterManager.cpp b/xpcom/base/nsMemoryReporterManager.cpp
index de76bd7d8afbd939226deb88384f1c73f23a572b..723cc6753205fab3f9576343e44a0d2a9d161ef0 100644
--- a/xpcom/base/nsMemoryReporterManager.cpp
+++ b/xpcom/base/nsMemoryReporterManager.cpp
@@ -1335,6 +1335,25 @@ class JemallocHeapReporter final : public nsIMemoryReporter {
     MOZ_COLLECT_REPORT(
       "heap-chunksize", KIND_OTHER, UNITS_BYTES, stats.chunksize,
       "Size of chunks.");
+
+#ifdef MOZ_PHC
+    mozilla::phc::MemoryUsage usage;
+    ReplaceMalloc::PHCMemoryUsage(usage);
+
+    MOZ_COLLECT_REPORT(
+      "explicit/heap-overhead/phc/metadata", KIND_NONHEAP, UNITS_BYTES,
+      usage.mMetadataBytes,
+"Memory used by PHC to store stacks and other metadata for each allocation");
+    MOZ_COLLECT_REPORT(
+      "explicit/heap-overhead/phc/fragmentation", KIND_NONHEAP, UNITS_BYTES,
+      usage.mFragmentationBytes,
+"The amount of memory lost due to rounding up allocations to the next page "
+"size. "
+"This is also known as 'internal fragmentation'. "
+"Note that all allocators have some internal fragmentation, there may still "
+"be some internal fragmentation without PHC.");
+#endif
+
     // clang-format on
 
     return NS_OK;