如何在Hotspot的Metaspace中测量碎片化?

3

我正在研究如何调试应用程序中的“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的本机内存?
这是使用Java HotSpot(TM) 64位服务器VM (25.45-b02) for linux-amd64 JRE (1.8.0_45-b14)。
2个回答

4
我刚刚研究了HotSpot中Metaspace的实现。Metaspace被分成块,并使用freelist进行管理。因此,碎片化确实是您遇到问题的可能原因。
我还查看了HotSpot VM的标志(-XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal),但在发行版本中没有标志。
然而,Metaspace类中有一个dump()方法,似乎可以通过设置-XX:+TraceMetadataChunkAllocation标志来触发。还有一个-XX:+TraceMetavirtualspaceAllocation,听起来对您很有兴趣。但是,这些都是“develop”标志,这意味着您需要调试版本的VM。

1
不错的发现,我正在构建一个调试版本并尝试它们。 - mabi

2

@loonytune的回答完全可行,但我想提供更多细节:

“元空间”是一个包含元空间集合的组合,每个类加载器都有一个元空间。每个元空间都保存了一个VirtualSpace对象列表,其中分配了不同大小的Metachunk。这些块保存了真正的元数据容器MetaBlock

我需要一个调试JRE来运行这些标志,所以按照此教程检查了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() 不可用8192个字,空间 @ 0x00007fee4cdb9350 128K,使用率94% [0x00007fedf5e22000,0x00007fedf5f13000,0x00007fedf5f22000,0x00007fedf6022000)。
这表明当前的VirtualSpace已满,并且无法容纳所请求的8192个字的另一个块。这将导致此元空间切换到另一个VirtualSpace。
当分配新的Meta chunk,如下所示:
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
第一种情况下,它是由128个字组成的小块,并使用了小块列表。您可以看到,下一个请求去到中等大小的块(大小为512),并在总共留下14个可用块。一旦可用块数达到0,就需要进行Full GC以增加总的Metaspace大小。
请注意,指定 -verbose 会从上述两个标志中获取更多输出。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接