如何测量 Hotspot 元空间中的碎片?

How do I measure fragmentation in Hotspot's Metaspace?

我正在研究调试应用程序中的 "OutOfMemoryError: Metaspace" 错误。在 OOME 之前,我在 gc 日志中看到以下内容:

{Heap before GC invocations=6104 (full 39):
 par new generation   total 943744K, used 0K [...)
  eden space 838912K,   0% used [...)
  from space 104832K,   0% used [...)
  to   space 104832K,   0% used [...)
 concurrent mark-sweep generation total 2097152K, used 624109K [...)
 Metaspace       used 352638K, capacity 487488K, committed 786432K, reserved 1775616K
  class space    used 36291K, capacity 40194K, committed 59988K, reserved 1048576K
2015-08-11T20:34:13.303+0000: 105892.129: [Full GC (Last ditch collection) 105892.129: [CMS: 624109K->623387K(2097152K), 3.4208207 secs] 624109K->623387K(3040896K), [Metaspace: 352638K->352638K(1775616K)], 3.4215100 secs] [Times: user=3.42 sys=0.00, real=3.42 secs] 
Heap after GC invocations=6105 (full 40):
 par new generation   total 943744K, used 0K [...)
  eden space 838912K,   0% used [...)
  from space 104832K,   0% used [...)
  to   space 104832K,   0% used [...)
 concurrent mark-sweep generation total 2097152K, used 623387K [...)
 Metaspace       used 352638K, capacity 487488K, committed 786432K, reserved 1775616K
  class space    used 36291K, capacity 40194K, committed 59988K, reserved 1048576K
}

据我所知,元空间容量甚至没有接近承诺的大小(在本例中,-XX:MaxMetaspaceSize=768m)。所以我怀疑元空间的碎片导致分配器无法为新类加载器找到新块。

我知道 -XX:PrintFLSStatistics 但它只涵盖 CMS,不涵盖本机内存。

所以我的问题是:是否有类似于 PrintFLSStatistics 的调试帮助可用于 Hotspot 的本机内存?

这是为 linux-amd64 JRE (1.8.0_45-b14) 使用 Java HotSpot(TM) 64 位服务器 VM (25.45-b02)。

我刚刚研究了 HotSpot 中元空间的实现。元空间被分成块并使用空闲列表进行管理。所以碎片确实是你的问题的可能原因。

我也查看了HotSpot VM的flags(-XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal),release版本没有flag。

不过,元空间class中有一个dump()方法,似乎是通过设置-XX:+TraceMetadataChunkAllocation标志来触发的。还有 -XX:+TraceMetavirtualspaceAllocation 听起来您可能会感兴趣。但是,这些是 "develop" 标志,这意味着您需要 VM 的调试版本。

@loonytune 的回答很好,但我想提供更多细节:

就上下文而言,“元空间”是元空间的集合,每个 class 加载器一个。每个元空间包含 VirtualSpace 个对象的列表,其中分配了 Metachunk 个不同大小的对象。这些块包含 MetaBlocks,它们是元数据的真正容器。

我需要一个调试 JRE 来 运行 这些标志,所以在 this tuorial 之后我检查了 openjdk 存储库(我将检查重命名为 vm 因为构建脚本似乎需要jdk8 文件夹名称有问题),运行

~/vm$ bash configure --enable-debug
~/vm$ DISABLE_HOTSPOT_OS_VERSION_CHECK=ok make all

并使用结果 vm/build/linux-x86_64-normal-server-fastdebug/images/j2re-image 作为我的 java 运行 时间。

生成的日志行如下所示:

VirtualSpaceNode::take_from_committed() not available 8192 words space @ 0x00007fee4cdb9350 128K, 94% used [0x00007fedf5e22000, 0x00007fedf5f13000, 0x00007fedf5f22000, 0x00007fedf6022000)

这表示当前 VirtualSpace 已满,无法容纳请求的 8192 word 大小的另一个块。这将导致此元空间切换到另一个 VirtualSpace.

ChunkManager::chunk_freelist_allocate: 0x00007fee4c0c39f8 chunk 0x00007fee15397400 size 128 count 0 Free chunk total 7680 count 15

ChunkManager::chunk_freelist_allocate: 0x00007fee4c0c39f8 chunk 0x00007fedf6021000 size 512 count 14 Free chunk total 7168 count 14

分配新的 Metachunk 时会发生这种情况,在第一种情况下,它有 128 个字大,并用完了小块列表。如您所见,下一个请求转到中等大小的块(大小为 512)并总共留下 14 个空闲块。一旦空闲总数达到 0,就需要 Full GC 来增加元空间的总大小。

请注意,指定 -verbose 可让您从上述两个标志获得更多输出。