JVM问题分析调优经验

一、前言

JVM 性能优化步骤:

  1. 预估系统参数
  2. 压测后,调整 JVM 参数
  3. 线上系统监控和优化
  4. 统一的 JVM 参数模板

线上频繁 Full GC 的表现:

  • 机器 CPU 负载过高

  • 频繁 Full GC 报警

  • 系统无法处理请求或者处理过慢

频繁 Full GC 常见原因:

  1. 对象频繁进入老年代,频繁触发 Full GC

    系统承载高并发请求,或处理数据量过大,导致 Young GC 频繁,每次 Young GC 过后存活对象太多,内存分配不合理,Survivor 区域过小。

  2. 系统一次性加载过多数据进入内存,大对象直接入老年代,频繁触发 Full GC

  3. 内存泄漏,对象无法回收,一直占用在老年代里,频繁触发 Full GC

  4. MetaSpace (永久代)加载类过多,触发 Full GC

  5. 代码中使用 System.gc(),触发 Full GC

针对以上 Full GC 常见的原因,对应的优化方式:

  1. jstat 分析,合理分配内存,调大 Survivor 区域

  2. dump 出内存快照,用 MAT 工具进行分析,代码上排查

  3. dump 出内存快照,用 MAT 工具进行分析,代码上排查

  4. 若内存使用不多,还频繁触发 Full GC,那么优化加载的类

  5. 若内存使用不多,还频繁触发 Full GC,代码上排查,删除 System.gc()

一、案例一:高分配速率(High Allocation Rate)

分配速率(Allocation rate)表示单位时间内分配的内存量。

通常使用 MB/sec 作为单位。上一次垃圾收集之后, 与下一次 GC 开始之前的年轻代使用量, 两者的差值除以时间, 就是分配速率。分配速率过高就会严重影响程序的性能, 在 JVM 中可能会导致巨大的 GC 开销。

  • 正常系统: 分配速率较低 ~ 回收速率 -> 健康
  • 内存泄漏: 分配速率 持续大于 回收速率 -> OOM
  • 性能劣化: 分配速率较高 ~ 回收速率 -> 亚健康



  1. JVM 启动之后 291 ms, 共创建了 33,280 KB 的对象。第一次 Minor GC(小型GC) 完成后, 年轻代中还有 5,088 KB 的对象存活。

  2. 在启动之后 446 ms, 年轻代的使用量增加到 38,368 KB , 触发第二次 GC, 完成后年轻代的使用量减少到 5,120 KB。

  3. 在启动之后 829 ms, 年轻代的使用量为 71,680 KB, GC 后变为 5,120 KB。


思考一个问题, 分配速率, 到底影响什么?

想一想, new 出来的对象, 在什么地方。

答案就是, Eden。

假如我们增加 Eden, 会怎么样。 考虑蓄水池效应。 最终的效果是, 影响 Minor GC 的次数和时间, 进而影响吞吐量。

在某些情况下, 只要增加年轻代的大小, 即可降低分配速率过高所造成的影响。

增加年轻代空间并不会降低分配速率, 但是会减少 GC 的频率。如果每次 GC 后只有少量对象存活, minor GC 的暂停时间就不会明显增加。

二、案例二:过早提升(Premature Promotion)

提升速率(promotion rate)用于衡量单位时间内从年轻代提升到老年代的数据量。

一般使用 MB/sec 作为单位, 和分配速率类似。

JVM 会将长时间存活的对象从年轻代提升到老年代。根据分代假设, 可能存在一种情况, 老年代中不仅有存活时间长的对象, 也可能有存活时间短的对象。

这就是过早提升: 对象存活时间还不够长的时候就被提升到了老年代。

major GC 不是为频繁回收而设计的, 但 major GC 现在也要清理这些生命短暂的对象, 就会导致 GC 暂停时间过长。这会严重影响系统的吞吐量。


GC 之前和之后的年轻代使用量以及堆内存使用量。

这样就可以通过差值算出老年代的使用量。

和分配速率一样, 提升速率也会影响 GC 暂停的频率。但分配速率主要影响 minor GC, 而提升速率则影响 major GC 的频率。

有大量的对象提升, 自然很快将老年代填满。老年代填充的越快, 则 major GC 事件的频率就会越高。


一般来说过早提升的症状表现为以下形式:

  1. 短时间内频繁地执行 full GC
  2. 每次 full GC 后老年代的使用率都很低, 在 10-20% 或以下
  3. 提升速率接近于分配速率

要演示这种情况稍微有点麻烦, 所以我们使用特殊手段, 让对象提升到老年代的年龄比默认情况小很多。 指定 GC 参数 -Xmx24m -XX:NewSize=16m -XX:MaxTenuringThreshold=1, 运行程序之后, 可以看到下面的 GC 日志:


解决这类问题, 需要让年轻代存放得下暂存的数据, 有两种简单的方法:

  1. 增加年轻代的大小, 设置 JVM 启动参数, 类似这样: -Xmx64m -XX:NewSize=32m, 程序在执行时, Full GC 的次数自然会减少很多, 只会对 minor GC 的持续时间产生影响。

  2. 减少每次批处理的数量, 也能得到类似的结果。至于选用哪个方案, 要根据业务需求决定。在某些情况下, 业务逻辑不允许减少批处理的数量, 那就只能增加堆内存, 或者重新指定年轻代的大小。 如果都不可行, 就只能优化数据结构, 减少内存消耗。

但总体目标依然是一致的: 让临时数据能够在年轻代存放得下。

#Java##程序员#
全部评论
第一次碰到这种问题
点赞 回复 分享
发布于 2022-10-20 11:00 陕西

相关推荐

时间线: 4.7投递4.13测评4.16一面4.22二面4.25主页显示挂了面试整体流程就是先提前进入荣耀给你发的邮件的链接,上面有个排队人数,这时候等就可以了。等到你了会给你发会议编号的手机短信,把编号填上去就可以直接见到面试官了。一面,全程拷打项目,八股就问了两三个很简单的:1.自我介绍2.定时任务的数据库表的组成是怎样的?每个表有哪些字段?3.定时项目这里面如果任务在某一环投递失败了怎么办 ?4.你觉得你的项目还有哪些可以提升的地方?(这个问题连问了我三次)5.Java内存结构了解吗?6.redis zset 的底层结构?跳表为什么这么快?查询的过程是怎样的?7.你平时都用哪些ai工具?用的过程有遇到哪些问题?ps:还有一些问题忘记了,主要是面试前有点紧张,忘录音了。但基本上都是在问项目,ai相关的好像一个没问。二面,全程33分钟左右,都是一些开放性的问题:1.在 AI 冲击下,为何仍选择投身软件开发行业?2.你对这个荣耀实习岗位的理解是什么?3.是否有认识的学长在荣耀工作?4.分享一个你引以为傲的经历。这段经历中你认为自己做得好的地方有哪些?5.日常使用哪些 AI 开发工具?6.是否关注 AI 工具的 token 消耗?如何经济地使用?7.你这个项目是独立完成还是团队协作?8.你在其中的角色是什么?9.你在项目中产生的独特价值是什么?10.你对荣耀公司及其岗位有何了解?11.是什么吸引你报名荣耀的岗位?12.使用过哪些具体荣耀产品?13.请简要介绍你的家庭和个人情况。14.目前是否有恋爱关系?15.对未来工作地点有何期望?16.未来更倾向从事哪个领域的软件开发?互联网、金融还是制造业?17.你有什么想了解的问题吗?总结:整体下来一二面的体验还不错,面试官都挺和蔼的,特别是二面的面试官感觉他说的比我还多。虽然最后挂了,但收获也蛮多的,再接再厉吧💪
查看23道真题和解析
点赞 评论 收藏
分享
评论
3
28
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务