一、99.9%的人没有JVM调优经验
其实,JVM调优并不是每个开发者都会接触到的技能,原因很简单:JVM通常不需要调优!
如果你的 JVM 真的出现了严重的 GC 压力,第一步想到的应该是优化代码,而不是直接上手去调整 JVM 参数。因为调整 JVM 可能带来以下问题:
- 不可维护:调优后,可能下一个维护者难以理解这些修改。
- 不可拓展:假设当前流量已经优化好了,但未来流量大幅变化时,这些参数可能失效。
尽管如此,面试时常常会有面试官问起 JVM 调优。那么,他们真的在问 JVM 调优的经验吗?
二、面试官实际上想问什么?
他们更多的是在考察以下方面:
- 你对 JVM GC 的理解
- 你对不同 GC 回收器的熟悉度
- 你是否了解常见的 JVM GC 参数配置
接下来我们以 Java 17 中的G1垃圾回收器为例,来看一些常见的 GC 相关参数。
在 G1 垃圾回收器中,堆内存被划分为多个Region,虽然这些 Region 的大小是相同的,但是它们属于不同的代。了解这些原理有助于我们更好地优化 JVM 的 GC 性能。
内存区域划分:
- E(Eden)区:存放新创建的对象
- S(Survivor)区:存放从Eden区存活下来的对象
- O(Old)区:存放长期存活的对象
- H(Humongous)区:专门存放超大对象
大对象的判定标准: 当一个对象的大小超过单个 Region(内存区域) 大小的 50%(0.5个Region)时,它就被视为大对象(Humongous对象)
三、常见的垃圾回收器
目前常见的垃圾回收器包括下面 3 种:
- CMS (Concurrent Mark-Sweep): JDK 14之后不再支持
- G1 (Garbage First): JDK 8-17版本的默认垃圾回收器
- ZGC (Z Garbage Collector): 从JDK 21开始,成为默认的垃圾回收器。
在启动 JVM 时,可以通过指定参数来使用不同的垃圾回收器。
-XX:+UseZGC
-XX:+UseG1GC
-XX:+UseConcMarkSweepGC
四、常见的调优策略
1. 设置堆大小
一般来说,我们建议将 JVM 的堆内存大小进行固定,避免堆内存发生动态扩缩容。
# 例如固定为2GB
-Xms 2048M -XMx 2048M
固定堆内存的好处:
- 避免堆的扩缩容:堆内存的扩缩容会影响 GC 的时间,带来更多的停顿时间,尤其是 G1这 种需要预测停顿时间的垃圾回收器,会导致预测变得不准确。
- 减少内存碎片:在 G1 中,堆内存被划分为多个 Region,如果堆大小不固定,Region 的大小也会不固定,可能导致内存浪费。
2. 设置Region大小
G1 将堆内存划分为多个大小相等的 Region,Region 的大小一般是 2 的幂,从 1MB 到 32MB 不等。
合理调整Region大小能更好地配合应用的实际需求,提升性能表现,减少停顿时间,并且优化堆内存管理。
对于较小的堆内存,建议设置较小的 Region 大小,例如2MB或4MB。 对于较大的堆内存,建议设置较大的Region大小。
# 例如设置Region 为4M
-XX:G1HeapRegionSize=4M
3. 设置最大目标停顿时间
上面说过 G1 垃圾回收器的最大特点是停顿预测模型。默认的停顿时间为 200ms。
因此我们可以设置一个目标停顿时间:
# 设置目标停顿时间为 100 ms
-XX:MaxGCPauseMillis=100
注意: JVM 会尽量保证垃圾回收停顿时间不超过这个值,但不能完全保证。所以针对一些延迟敏感的场景,可以适当调小停顿时间。
4. 设置标记线程数和回收线程数
G1 在进行标记对象为垃圾和回收垃圾时,都是多线程操作的。 因此我们可以根据需要去调整线程数。
# 并行回收线程数(默认值是 CPU 核心数)
-XX:ParallelGCThreads=8
# 并发标记线程数
-XX:ConcGCThreads=4
说到最后,这些调优参数在哪使用呢? 其实是在我们运行服务时设置的,例如:
# -Xms4G:设置JVM堆内存的初始大小为4GB。这意味着JVM启动时会分配4GB的堆内存
# -Xmx4G:设置JVM堆内存的最大大小为4GB。JVM堆的大小不会超过4GB
# -XX:+UseG1GC:启用G1垃圾回收器
# -XX:MaxGCPauseMillis=200:设置G1垃圾回收器的最大目标停顿时间为200 毫秒
# -XX:G1HeapRegionSize=4M:设置G1垃圾回收器的Region大小为4MB
# -XX:InitiatingHeapOccupancyPercent=45:设置堆的占用比例为45%。当堆内存的占用达到该比例时,G1垃圾回收器会开始触发并发标记过程,准备进行垃圾回收
# -Xlog:gc*,gc+heap=debug:file=gc.log:启用GC日志记录,gc*表示记录所有GC日志,gc+heap=debug表示记录GC过程中堆内存的详细调试信息,并将日志输出到gc.log文件中。
java -Xms4G -Xmx4G \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:G1HeapRegionSize=4M \
-XX:InitiatingHeapOccupancyPercent=45 \
-Xlog:gc*,gc+heap=debug:file=gc.log \
-jar my-app.jar