写点什么

[JVM 面试]Full GC 到底是如何产生的?如何解决?

  • 2022 年 5 月 15 日
  • 本文字数:1827 字

    阅读完需:约 6 分钟



下图为与产生Full GC相关的内存区域,初生代、老年代、以及Metaspace区域。


内存区域


内存区域

System.gc()方法的调用

在代码中调用System.gc()方法会建议JVM进行Full GC,但是注意这只是建议,JVM执行不执行是另外一回事儿,不过在大多数情况下会增加Full GC的次数,导致系统性能下降,一般建议不要手动进行此方法的调用,可以通过


-XX:+ DisableExplicitGC 来禁止 RMI 调用 System.gc。

老年代(Tenured Gen)空间不足

Survivor区域的对象满足晋升到老年代的条件时,晋升进入老年代的对象大小大于老年代的可用内存,这个时候会触发Full GC

Metaspace 区内存达到阈值

从 JDK8 开始,永久代(PermGen)的概念被废弃掉了,取而代之的是一个称为Metaspace的存储空间。Metaspace使用的是本地内存,而不是堆内存,也就是说在默认情况下Metaspace的大小只与本地内存大小有关。


-XX:MetaspaceSize=21810376B(约为 20.8MB)


超过这个值就会引发 Full GC,这个值不是固定的,是会随着 JVM 的运行进行动态调整的,与此相关的参数还有多个,详细情况请参考这篇文章


http://www.importnew.com/22886.html ( jdk8 Metaspace 调优)

统计得到的 Minor GC 晋升到旧生代的平均大小大于老年代的剩余空间

Survivor区域对象晋升到老年代有两种情况:


  • 一种是给每个对象定义一个对象计数器,如果对象在 Eden 区域出生,并且经过了第一次 GC,那么就将他的年龄设置为 1,在 Survivor 区域的对象每熬过一次 GC,年龄计数器加一,等到到达默认值 15 时,就会被移动到老年代中,默认值可以通过


-XX:MaxTenuringThreshold 来设置。


  • 另外一种情况是如果 JVM 发现 Survivor 区域中的相同年龄的对象占到所有对象的一半以上时,就会将大于这个年龄的对象移动到老年代,在这批对象在统计后发现可以晋升到老年代,但是发现老年代没有足够的空间来放置这些对象,这就会引起 Full GC。

堆中产生大对象超过阈值

这个参数可以通过


-XX:PretenureSizeThreshold


进行设定,大对象或者长期存活的对象进入老年代,典型的大对象就是很长的字符串或者数组,它们在被创建后会直接进入老年代,虽然可能新生代中的 Eden 区域可以放置这个对象,在要放置的时候 JVM 如果发现老年代的空间不足时,会触发 GC。

老年代连续空间不足

JVM 如果判断老年代没有做足够的连续空间来放置大对象,那么就会引起 Full GC,例如老年代可用空间大小为 200K,但不是连续的,连续内存只要 100K,而晋升到老年代的对象大小为 120K,由于 120>100 的连续空间,所以就会触发 Full GC。

CMS GC 时出现 promotion failed 和 concurrent mode failure

《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 个原因引发的 Full GC 可以参考这篇文章,下面也摘抄自这篇文章:


http://www.importnew.com/22886.html (JVM 调优 —— GC 长时间停顿问题及解决方法)


提升失败(promotion failed),在 Minor GC 过程中,Survivor Unused 可能不足以容纳 Eden 和另一个 Survivor 中的存活对象, 那么多余的将被移到老年代, 称为过早提升(Premature Promotion)。这会导致老年代中短期存活对象的增长, 可能会引发严重的性能问题。?再进一步, 如果老年代满了, Minor GC 后会进行 Full GC, 这将导致遍历整个堆, 称为提升失败(Promotion Failure)。


在 CMS 启动过程中,新生代提升速度过快,老年代收集速度赶不上新生代提升速度。在 CMS 启动过程中,老年代碎片化严重,无法容纳新生代提升上来的大对象,这是因为 CMS 采用标记清理,会产生连续空间不足的情况,这也是 CMS 的缺点


总结


--


可以发现其实堆内存的 Full GC 一般都是两个原因引起的,要么是老年代内存过小,要么是老年代连续内存过小。无非是这两点,而元数据区 Metaspace 引发的 Full GC 可能是阈值引起的,详细原因还是建议参考其他文章,我就不误人子弟了。


检测 JVM 堆的情况




可以使用 JDK 的 bin 目录下的 jvisualvm.exe 工具来进行实时监测,这个是图形化界面,最为直观,这是一个强大的工具。


采用 jps 找到进行 id,然后使用 jstat -gc pid 来实时进行检测。


运行程序前设置


-XX:+PrintGCDetails,-XX:+PrintGCDateStamps 参数打印 GC 的详细信息进行分析。


监测图


监测图


测试


测试


命令


命令


解决策略




如果是发现由于老年代内存过小频繁引起的Full GC,那么可以适当增加老年代的内存大小,如果是发现是由于老年代没有连续空间来让初生代的对象晋升,如果是采用CMS,那么可以设置进行 n 次?CMS后进行一次压缩式Full GC,参数如下:


-XX:+UseCMSCompactAtFullCollection:允许在 Full GC 时,启用压缩式 GC

用户头像

还未添加个人签名 2022.04.13 加入

还未添加个人简介

评论

发布
暂无评论
[JVM面试]Full GC 到底是如何产生的?如何解决?_Java_爱好编程进阶_InfoQ写作社区