理论知识
触发 GC 原因
Minor GC
- 年轻代空间不足(指的是Eden,Survivor满不会触发GC)
- 老年代空间不足可能会触发 Minor GC
Major GC
- 老年代空间不足
Full GC
- 老年代空间不足
- 方法区空间不足
- 调用 System.gc()时(建议执行 Full GC,不是绝对)
- Minor GC 后进入老年代对象的平均大小 > 老年代的可用内存
- 产生的对象 > 老年代的可用内存
OOM原因
- 内存泄露
程序中的对象不会再使用了,且GC不能进行回收。
- 内存溢出
1、JVM堆内存设置不合理
2、 程序中创建了大量对象,并且长时间不能被GC回收
3、内存泄露会导致内存溢出
调优目的
GC优化的目的有两个
- 将转移到老年代的对象数量降低到最小
- 减少 Full GC 的执行频率和执行时间
调优工具
小编在这列举几个常用的调优工具,挑选两三个作为平时常用的即可。
- JDK命令行
- Jconsole
- JvisualVM
- MAT
- Jprofiler
- GCViewer
- GCEasy
调优步骤
JVM调优简要概括为三部曲,先要对内存进行监控,了解内存中的实际情况,再对监控的数据结果进行分析,经过一系列分析后,再根据分析结果判断是否需要优化,思考如何优化等。
监控
调优过程的第一步,下面列举一些使用各种JVM工具需监控的数据(当然不只这些数据)
1、监控宿主机 cpu,内存 等状态信息
2、jdk版本
3、查看系统日志
4、了解JVM参数设置
5、下载 dump 文件
- 建议添加参数:-XX:HeapDumpOnOutOfMemoryError,出现OOM时下载dump文件;并且指定 dump 文件地址,参数:-XX:HeapDumpPath=<path>
6、GC频率时间等信息
7、各线程状态以及执行情况
8、大对象的占比,实例数等
分析
根据当前监控到的程序的情况以及设置的程序JVM参数,分析是否需要进行优化。如果各项参数设置合理,GC频率不高,GC耗时不高,系统没有出现异常情况等则不需要进行优化。
反之,如果出现以下情况,则需要进行调优:
- Minor GC / Full GC执行时间长(超过1-3s)
- Minor GC / Full GC执行频率高(10min以内/次)
- 每次 Major GC 结束老年代内存占用曲线呈上升趋势(有内存泄露的可能)
如何调优
调优就是不断的实验和试错的过程,经过分析并找到最合适的参数和设置。调优需遵循的原则如下:
- 频繁 Minor GC
- 较少 Major GC
- 基本不动 Metaspace
JVM调优参数参考
-
减少使用全局对象和大对象
-
调整新生代和老年代的比例到最合适,什么比例才最合理?
1)更大的年轻代必然导致更小的老年代,大的年轻代会延长YGC的周期,但会增加每次 YGC 的时间;小的老年代会导致更频繁的 Full GC
2)更小的年轻代必然导致更大的老年代,小的年轻代会导致YGC频繁,但每次YGC时间会更短,大的老年代会减少 Full GC 的频率
如何选择应该依赖应用程序对象生命周期的分布情况,如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,老年代应适当增大。 -
选择合适的GC收集器
根据主机cpu的核数选择串行/并行的GC收集器
-
针对JVM堆的设置,一般通过 -Xms -Xmx 设置初始堆大小和最大堆大小,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,通常把 -Xms -Xmx 设置为相同的值
列举常见问题的解决思路
1、cpu load 过高
- 找出哪个进程cpu占用高(top)
- 找出该进程的哪个线程cpu占用高(top -H -p pid)
- 把进程号转 16 进制(printf %x pid)
- 打印出错的线程堆栈信息(jstack <pid> | grep <thread-hex-id> -A 10,其中 -A 10 参数用来指定显示行数)
重点关注(WAITING/BLOCKED)找到线程中 waiting on <xx> 的有哪些,找到是哪个线程持有 <xx> 这把锁。
查看线程中是否出现(deadlock)死锁,死锁和频繁的 full gc 往往是导致 cpu load 飙升的原因。
2、系统内存占比过高
- 导出堆内存dump(jmap)
- 使用jvm工具分析