1. 频繁 Full GC 怎么排查?

这种问题最好的办法就是结合有具体的例子举例分析,如果没有就说一般的分析步骤。发生FGC有可能是内存分配不合理,比如Eden区太小,导致对象频繁进入老年代,这时候通过启动参数配置就能看出来,另外有可能就是存在内存泄露,可以通过以下的步骤进行排查:

1.1 使用jstat -gcutil命令或者查看gc.log日志,查看内存回收情况。

JVM 调优 - 图1

  • S0 S1 分别代表两个 Survivor 区占比。
  • E 代表 Eden 区占比,图中可以看到使用78%。
  • O 代表老年代,M 代表元空间,YGC 发生54次,YGCT 代表 YGC 累计耗时,GCT 代表 GC 累计耗时。

JVM 调优 - 图2

  • [GC [FGC 开头代表垃圾回收的类型。
  • PSYoungGen: 6130K->6130K(9216K)] 12274K->14330K(19456K), 0.0034895 secs代表YGC前后内存使用情况。
  • Times: user=0.02 sys=0.00, real=0.00 secs,user 表示用户态消耗的 CPU 时间,sys 表示内核态消耗的 CPU 时间,real 表示各种时钟的等待时间。

这两张图只是举例并没有关联关系,比如你从图里面看能到是否进行FGC,FGC的时间花费多长,GC后老年代,年轻代内存是否有减少,得到一些初步的情况来做出判断。

1.2 dump出内存文件再具体分析

比如通过jmap命令jmap -dump:format=b,file=dumpfile pid,导出之后再通过Eclipse Memory Analyzer等工具进行分析,定位到代码,修复。

2. CPU飙高,同时FGC怎么办?

这里的处理和上面的方法类似:

  1. 找到当前进程的pid,top -p pid -H 查看资源占用,找到线程
  2. printf “%x\n” pid,把线程pid转为16进制,比如0x32d
  3. jstack pid|grep -A 10 0x32d查看线程的堆栈日志,还找不到问题继续
  4. dump出内存文件用MAT等工具进行分析,定位到代码,修复

    3. JVM 调优有什么经验吗?

    要明白一点,所有的调优的目的都是为了用更小的硬件成本达到更高的吞吐,JVM 的调优也是一样,通过对垃圾收集器和内存分配的调优达到性能的最佳。

    3.1 简单的参数含义

    首先,需要知道几个主要的参数含义。
    JVM 调优 - 图3

  5. -Xms设置初始堆的大小,-Xmx设置最大堆的大小

  6. -XX:NewSize年轻代大小,-XX:MaxNewSize年轻代最大值,-Xmn则是相当于同时配置-XX:NewSize和-XX:MaxNewSize为一样的值
  7. -XX:NewRatio设置年轻代和年老代的比值,如果为3,表示年轻代与老年代比值为1:3,默认值为2
  8. -XX:SurvivorRatio年轻代和两个Survivor的比值,默认8,代表比值为8:1:1
  9. -XX:PretenureSizeThreshold 当创建的对象超过指定大小时,直接把对象分配在老年代。
  10. -XX:MaxTenuringThreshold设定对象在Survivor复制的最大年龄阈值,超过阈值转移到老年代
  11. -XX:MaxDirectMemorySize当Direct ByteBuffer分配的堆外内存到达指定大小后,即触发Full GC

    3.2 调优

  12. 为了打印日志方便排查问题最好开启GC日志,开启GC日志对性能影响微乎其微,但是能帮助我们快速排查定位问题。-XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:gc.log

  13. 一般设置-Xms=-Xmx,这样可以获得固定大小的堆内存,减少GC的次数和耗时,可以使得堆相对稳定
  14. -XX:+HeapDumpOnOutOfMemoryError让JVM在发生内存溢出的时候自动生成内存快照,方便排查问题
  15. -Xmn设置新生代的大小,太小会增加YGC,太大会减小老年代大小,一般设置为整个堆的1/4到1/3
  16. 设置-XX:+DisableExplicitGC禁止系统System.gc(),防止手动误触发FGC造成问题

参考

  1. JVM 夺命连环问