JVM优化实战总结

YGC风险和调优:一般的应用服务器的配置是2C4G或4C8G,E区内存分配在1G左右,正常的YGC耗时最多几十毫秒,然后E区增速不快,YGC频率不高,用户使用时是无感知的。
  • 风险:
    • 机器升级:很多系统在流量大后,低配置机器无法满足性能要求。会升级到大机器,类似于数据计算的系统对于内存要求高,如32C64G。在这种配置下E区分配内存也会增大,回收这么多对象,YGC的STW时间就会增加,极端情况可能要几秒钟回收。并且由于请求量很大,E区的新对象会不断的产生,造成E区内存增速很快,YGC变得频繁。就会造成系统频繁的卡顿,影响用户使用。
    • 分配不合理:在系统上线前,未根据预期并发量、平均每个任务的内存需求大小去评估机器配置、集群规模。未合理的分配好E区的大小,导致E区增速过快,YGC频繁。
  • 优化:可根据预期并发量和任务内存需求大小,评估E区对象增速,合理分配E区大小,保障YGC的耗时和频率。对于高配机器,可以利用G1回收器去设置预期回收停顿时间,来保证不导致系统卡顿、假死的问题。
FGC风险和表现:
  • 风险:FGC非常消耗CPU资源,且FGC的过程会带来一定STW,系统可能时不时卡死。同时FGC触发时,如果碰上了程序有问题,有对象长期驻留内存无法回收,就会引发OOM,造成系统崩溃
  • 表现:机器CPU负载过高、频繁FGC报警、系统无法处理请求或者处理过慢
FGC问题的发生情况:
  1. 系统承载高并发请求,或者处理数据量过大,导致YGC频繁,而每次YGC后存活对象太多,内存分配不合理,S区过小,导致对象频繁进入老年代,频繁触发FGC;解决:通过jstat分析对应进程的内存和GC情况,看看内存区域分配是否合理
  2. 系统一次性加载过多数据进内存,搞出来很多大对象,导致频繁有大对象进入老年代,触发FGC;系统发生内存泄露,内存里驻留了大量对象塞满老年代,导致稍微有一些对象进入老年代就引发FGC。解决:通过jstat分析得到可能存在的原因,结合MAT分析内存快照定位。如果jstat分析发现内存使用不多,还频繁FGC,可能是:永久代里类太多,触发FGC或误调用System.gc触发FGC,该方法执行每次会指挥jvm尝试执行一次FGC。在jvm参数中加入-XX:DisableExplicitGC:该参数意思是禁止显示执行GC,不允许代码来触发。
JVM性能调优思路:
  1. 系统开发完后:根据QPS和对象大小计算E区塞满时间,得到YGC频率。估算YGC发生后会有多少对象存活,会有多少对象晋升,老年代对象增速、FGC频率。通过估算后得到一个较为合理的数据,然后分配新老年代空间,E区和S区的比例。原则就是尽可能让每次YGC后存活的对象远远小于S区,避免对象频繁进入老年代触发FGC。
  2. 压测阶段:新系统上线前进行压测,模拟线上压力,利用jstat工具观察JVM运行内存模型:E区对象增速、YGC频率、YGC耗时、YGC后存活对象大小、老年代增速、FGC频率、FGC耗时。根据上述指标合理分配内存
  3. 上线后:上线后需要做线上系统 监控,小公司不上监控软件就可以运行jstat,然后把监控信息写入文件,每日定时看看。有能力就上监控平台,监控机器资源负载情况(CPU、磁盘、内存、网络)、JVM的GC频率和内存使用率、系统自身业务指标、系统异常报错。
  4. 问题定位:是E区太小YGC太频繁?S区大小对象晋升老年代过多?大对象晋升阈值设置?动态年龄判断的阈值?针对上述数据进行问题定位后,采取对应优化措施。
  5. 内存评估:根据每次YGC的存活,评估S区大小设置是否合理、评估多久进行一次FGC,产生的STW是否可以接受
  6. 如果负载增加10倍,100倍:增加服务器数量,根据负载的增比。同比增加机器数量,机器配置、使用更高配置机器、意味着更快速的处理速度和更大内存。响应时间敏感且内存大的,建议采用G1
  7. 优化原则:尽量让每次YGC后的存活对象小于S区的50%,都留存在年轻代,尽量不让对象进入老年代,减少FGC频率,避免频繁FGC对JVM性能影响
如何根据线上监控来排查问题:
  • 首先看下机器资源负载情况,比如看下CPU使用率,如果过高就检查下为何CPU那么高
  • 看下磁盘IO负载、看下磁盘发生了多少数据量IO,一些IO的耗时。关注一下本地磁盘的使用量和剩余空间,有时候一些系统bug会一直往本地磁盘写东西。
  • 然后就是机器内存使用量,看下JVM监控,了解各个内存区域使用量变化,FGC频率
  • 最后就是机器上的网络负载,看看通过网络IO读写了多少数据,一些耗时。
  • 一般情况就是CPU、内存、FGC。还有一些业务报警,比如一些限流,恶意请求,代码异常捕获也要接入警报,有问题上报到监控平台。
服务假死排查:接口无法调用,并不是直接抛出了OOM异常,因此很难去直接看线上日志,针对服务假死可以先通过命令去检查资源使用量
  • 服务假死的两种可能性:
    • 服务可能使用大量内存,内存始终无法释放,因此导致了频繁GC
    • 机器CPU负载太高,也许是某个进程耗尽了CPU资源,导致服务的线程始终无法得到CPU资源去执行,也就无法响应接口调用请求。
  • 排查:
    • cpu占比高:
      • 通过top命令检查进程消耗资源情况,如果CPU消耗很高,执行top -c显示进程列表,输入P,按照CPU使用率排序。
      • 定位耗费CPU的进程、然后执行top -Hp 进程id,输入P,按CPU使用率排序,定位进程中耗费CPU的线程。
      • 使用printf %x\n命令把线程pid转成16进制,然后通过jstack 进程id|grep 16进制编码打印出堆栈信息,通过堆栈信息确认有问题的代码。
    • CPU占比低但内存使用率高:
      • CPU消耗低但是内存消耗高,代表服务进程几乎快把分配给它的内存消耗完了,如果长期保持这个消耗量,说明GC时候内存并没有被回收
      • 内存使用率高了会引发:频繁的FGC,GC带来STW影响服务、内存使用率过多,JVM发生OOM、内存使用率过高,可能导致这个进程因为申请内存不足,直接被操作系统把这个进程杀掉
      • 使用jstat分析jvm运行情况,内存使用率很高,频繁gc,但是GC耗时很短,说明这个情况正常。并且频繁GC的时候,服务也没有反馈假死,可以排除是FGC带来的问题
      • 如果JVM发生OOM挂了,挂掉的时候服务就无法访问了,但是反馈的是假死,而不是服务不可用。并且日志里未看到OOM异常,就可以排除不是发生了OOM
      • 剩下的可能就是进程申请内存过多,导致内存不足,os可能杀死这个进程。导致服务无法访问,但是进程监控有脚本,进程被杀掉了,会有脚本自动把进程重启,所以过一会儿可能其他服务访问又可以。造成假死。在线上系统运行一段时间后,同top命令和jstat命令观察一段时间,发现jvm耗费超过50%内存时,迅速导出内存快照,用MAT分析。定位到大对象的产生原因。
内存泄露处理:
  • 通过jstat去定位是否是内存分配不合理,或者去看下是否永久代的类过多
  • 如果不是,则通过jmap导出内存快照,利用jhat、MAT工具来分析内存
  • MAT分析:点击Leak Suspects进行内存泄露分析,会显示一个饼图,提示哪些对象占用内存过大
  • 然后针对大对象实例,找开发去排查系统的代码问题,为何会创建大量对象,而且始终回收不掉
MAT分析内存快照:
  • 点击或有提示Leak Suspect Repost,进行内存泄露的分析
  • 饼图会展示大对象内存占用情况,可能存在内存泄露。并会有详细的问题告诉你什么结构占据了大量内存
  • 通过Details查看详细说明,查看到底是什么对象在内存里占据了过大的内存
  • 果发现了某个线程在执行过程中创建了大量对象,就可以点击See stacktrace,看到线程执行代码堆栈的调用链。找到是哪个方法造成了大量对象
posted @ 2025-04-16 17:10  难得  阅读(67)  评论(0)    收藏  举报