Full GC后GC日志中元空间大小未变化,但MemoryPoolMXBean与VisualVM显示其已缩小的原因分析
这个问题我之前维护Java 8应用时也碰到过,结合你给出的GC日志和Java 1.8.0_45的版本信息,核心原因可以从三个方面来解释:
统计维度不一样:日志看的是committed内存,监控看的是used内存
你GC日志里的[Metaspace: 112375K->112375K(1150976K)],前两个数值代表的是Metaspace的committed内存大小——也就是JVM已经从操作系统申请到的内存总量。而MemoryPoolMXBean和VisualVM显示的是used内存大小,也就是当前真正被类元数据占用的内存。
当Full GC触发类卸载后,JVM会释放类元数据占用的内存(所以used会下降),但不会把多余的committed内存立刻还给操作系统,而是留作空闲内存供后续类加载复用。这就导致日志里的committed数值没变化,但监控工具能看到实际使用的内存已经缩小了。Java 8早期版本的GC日志统计bug
Java 1.8.0_45属于比较老的版本,HotSpot VM在这个版本里有个已知的小问题:CMS收集器触发的Full GC中,GC日志对Metaspace used内存的统计没有正确更新,依然用了GC开始前的数值,但JVM内部其实已经完成了类卸载和内存释放。而MXBean和VisualVM是直接从JVM的内存管理接口拿实时数据,所以能展示真实的used大小。这个bug在后续的Java 8更新版本(比如u101及以后)里被修复了。CMS收集器的Metaspace回收策略
CMS收集器处理Metaspace时,类卸载后只会把对应的内存块标记为空闲,不会主动压缩或者把committed内存释放回操作系统。只有当Metaspace的空闲内存达到某个阈值,或者后续类加载需要更多内存时,才会调整committed的大小。所以即使used已经下降,committed依然保持原样,这就造成了日志和监控工具的差异。
如果你想验证这个结论,可以尝试多触发几次Full GC后再观察,或者升级到Java 8u101以上的版本,看看GC日志里的Metaspace数值会不会和监控工具一致。
内容的提问来源于stack exchange,提问作者Ryze

