其实工作中,很少有机会能接触到 jvm 调优,大部分时间都是在写 CRUD 代码,但如果万一线上真的出问题了,那么再去想 jvm 调优就有点晚了,所以我们需要先把这部分知识储备起来。
面试官思路:主要是想看下你对造成 JVM 性能问题有没有思考总结过。
可以从三个方面说:
1. 工作中引起 JVM 性能问题的原因到底是代码问题还是 JVM 参数问题?
2. JVM 性能问题如何监控和排查?
3. 如何根据性能问题进行参数调优?
代码排查
首先第一个方面,其实大部分 JVM 性能问题,并不是我们设置的参数问题,一般情况下,都是用默认参数就搞定了,而真正出问题的情况多是自己写的代码有问题,如频繁创建大对象,然后又引用它们不释放,然后这些大对象进入了老年代后,垃圾收集器有回收不了它们,老年代内存不足,造成频繁 Full GC,每次 Full GC 都会触发 STW,也就是造成卡顿现象,这样性能不就很差了吗?
如何监控
这个就是为了记录日志用的,我们可以利用日志来快速定位性能问题。
记录日志就是这个命令了:
-XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:gc.log
然后还可以设置内存溢出后自动导出Dump文件:
-XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\jvm.dump
另外如果想立即导出 dump 文件,用这个命令就可以了:
jmap -dump:format=b,file=D:/demo.hprof pid
当然,我们最好是能把 dump 文件获取到,然后放到本地的工具中分析就好办了。
如何分析 dump 文件
第二个方面中,拿到 dunp 文件后,就是分析:
说到常用的分析工具,当然是少不了 jvisualvm 可视化工具了,可以通过输入命令 jvisualvm 打开,然后载入之前的 dump 文件就可以了。这个工具会显示现在有哪些大对象占用着内存在。另外也可以通过 JProfiler 可视化工具来排查。
整体思路就是拿到 dump 文件,放到可视化工具中分析一把,大部分情况都是大对象造成的,然后再结合自己的代码,看看哪个地方造成了对象创建后没有被回收,然后优化代码就好了。
如何排查 Full GC
有时候,我们只能在线上的服务器上通过命令排查,那么就只能使用命令行工具来排查了,其实思路也很常规:
- jps -l 找到当前进程的pid
- ps -mp-o THREAD,tid,time 定位到具体线程。
- printf “%x\n”,把线程 pid 转为16进制,比如 0xf58
- jstack pid | g rep -A 10 0xf58 查看线程的堆栈日志,还找不到问题继续。
- 实在没办法了,只能 dump 出内存文件用可视化工具进行分析了,然后 定位到代码后修复。
分析 YGC
大多数情况下,新创建的对象都会在新生代的 Eden 区中分配,当 Eden 区没有足够的空间分配时,虚拟机将会发生一次 Minor GC,也就是 YGC。频繁发生 YGC 也是会对性能造成影响的。
分析年轻代对象增长速率。
每5秒执行一次,执行10次,然后观察这50秒内 eden 区增加的趋势,即可知道年轻代对象增长的速率。
jstat -gc pid 5000 10
思路:如果 eden 区增长很快,那么发生 YGC 的频率也会很高,说明 Eden 区太小了,可以调大 Eden 区(调整 -Xmn 参数),然后再次进行测试,看小是否减少了 YGC 回收频率。
另外如果 YGC 后,存活的对象超过了 Survivor 的 50%,则会进入老年代。
我们的调优思路是尽量减少对象进入老年代,以减少发生 FGC 的频率。所以通过调整 Eden 区的大小,减少了对象进入老年代的频率。
参数调优
第三个方面,如何进行参数调优。一般情况下,参数用默认的就好了,但是某些场景还是要进行参数调优的。
调优思路如下:
- 第一步肯定是看下到底配置了哪些参数:jinfo -flags pid。
- 然后看下 Java 的版本,Java 7 和 Java 8 差别有点大的,Java 8 取消了永久区,新增了 metaspace 区,具体对 垃圾回收的影响,放到后面分享。查看 Java 版本的命令:jinfo -sysprops pid。
- 一般设置-Xms=-Xmx,这样可以获得固定大小的堆内存,减少GC的次数和耗时,可以使得堆相对稳定。
- -Xmn 设置新生代的大小,太小会增加 YGC,太大会减小老年代大小,一般设置为整个堆的1/4到1/3。
- 设置-XX:+DisableExplicitGC禁止系统System.gc(),防止手动误触发FGC造成问题。