我找到了原因,又找到了。现象是系统一直在进行FullGC,启动后不久就会发生FullGC。这种现象比较少见,于是向他要了GC日志,看到了如下日志:这个很明显是达到了Metaspace的阈值触发了FullGC,但是看Metaspace的大小,大概用了134M,所以我问他设置了多少MetaspaceSize和MaxMetaspaceSize,告诉我设置是256M,那么有多少?奇怪的是:为什么Metaspace启动不久就触发了FullGC?从使用率来看,还没有达到门槛。FullGC之后还能正常运行一段时间,说明Metaspace确实恢复了。先说一个JVM的BUG从上面的GC日志中,我们可以看到在FullGC前后,Metaspace的使用量从137752K->71671K变化。其实如果你用Oracle官方的JDK,你会看到137752K->137752K,也就是没有变化。Metaspace好像还没有被回收。其实这是JVM的一个bug。我们的alijdk已经修复了这个问题,可以看到前后都有变化。所以如果你正在排查Metaspace的问题,希望你不要被这些信息骗了。我们来谈谈GC日志。从JDK8开始,任何GC都会默认打印GCCause,所以可以看到上面的FullGC是由MetadataGCThreshold触发的,也就是Metaspace提交的内存加上本次要分配的内存。已达到MetaspaceSize阈值。如果是JDK7(之前的版本不支持),可以通过添加JVM参数-XX:+PrintGCCause来打印原因,可以点击下面小程序中的链接查看这个的具体用法和含义参数:多说一点,Metaspce触发的GC都是FullGC。另外,大家经常看到的像下面的AllocationFailure这样的GCCauses其实都是正常的,因为大部分的GC,尤其是YGC,都是因为内存分配失败而触发的,所以不要看到Failure就认为有问题。为什么在使用率这么低的情况下会触发FullGC?Metaspace会触发FullGC。是因为Metaspace提交的内存加上本次要分配的内存的总和超过了阈值,但是我们看到使用量只有134M,而阈值是256M,那么你可能会怀疑以下两种情况:本次分配的内存达到122M以上?碎片化问题?对于第一种情况,基本上是不可能的,因为一个类不可能需要这么大的内存,所以暂时排除这种可能。对于第二种情况,有一个场景是可以满足的。创建的类加载器很多,但是每个类加载器加载的类很少,同时FullGC后可以快速回收。为了验证第二种情况,我尝试添加两个参数-XX:+HeapDumpBeforeFullGC和-XX:+HeapDumpAfterFullGC,分别对FullGC前后的内存进行dump。对于这两个参数,您可以通过下面的图像小程序链接查看具体用法。从两个转储的分析结果来看,我们检查了类加载器的状态。果然,我们在FullGC之前看到了31650个类加载器,FullGC之后,类加载器的数量变成了872个,于是我们开始查找有哪些类加载器,最后发现是某个类加载器对象太多了类型。咨询业务方后,确实存在这种情况。因为缓存没有做好,导致没完没了的创建了太多的类加载器。为什么FullGC类加载器会创建太多?一个问题是类加载器在第一次加载一个类时,会在Metaspace中分配一块内存。为了高效分配,每个类加载器用来存储类信息的内存块都是独立的,所以即使你的类加载器只加载了一个类,它也会给类加载器分配一块空内存,实际上至少是两个内存块,所以你可能会发现Metaspace的内存使用率很低,但是committed内存已经达到阈值,从而触发FullGC。如果类加载器太多,只加载几个类,结果就是很多碎片JVMPocket是我最近在鼓捣的一个微信小程序。您可以搜索JVMPocket或从我的公众号菜单中输入它。这个小程序的诞生主要是因为JVM的参数。有人问我相关问题,告诉我他们的参数可以解决,但是参数太长记不住,很尴尬。有了JVMPocket之后,可以直接找到对应的参数,发个链接,看看对应参数的具体含义、用法、默认值以及大家的看法。使用建议等,希望这个小程序也能帮到你。如果你对JVM参数有经验,可以在相应参数下留言,让更多人知道背后的故事。JVM掌上JVM参数提示【本文为专栏作者李家鹏原创文章,转载请通过微信公众号(你个假笨蛋,id:lovestblog)联系作者授权】点此查看作者更多好文作者
