JVM调优
大约 6 分钟
JVM调优
核心理论
1.1 JVM参数分类
JVM参数根据稳定性和标准化程度分为三类:
1.1.1 标准参数(Standard Options)
- 以
-
开头,所有JVM实现都必须支持 - 保持向后兼容
- 示例:
-version
、-help
、-cp
/-classpath
1.1.2 非标准参数(Non-Standard Options)
- 以
-X
开头,特定JVM实现支持的扩展参数 - 可能在不同版本间变化
- 示例:
-Xms
、-Xmx
、-Xmn
、-Xss
1.1.3 高级参数(Advanced Options)
- 以
-XX:
开头,用于高级调优和调试 - 稳定性差,可能随时移除
- 分为布尔型参数和键值对参数:
- 布尔型:
-XX:+<option>
(启用)、-XX:-<option>
(禁用) - 键值对:
-XX:<option>=<value>
- 布尔型:
1.2 内存管理参数
1.2.1 堆内存参数
-Xms<size>
:初始堆大小,默认物理内存的1/64-Xmx<size>
:最大堆大小,默认物理内存的1/4-Xmn<size>
:新生代大小(Eden + 2*Survivor)-XX:NewRatio=<n>
:新生代与老年代比例(老年代/新生代 = n),默认2-XX:SurvivorRatio=<n>
:Eden区与Survivor区比例(Eden/Survivor = n),默认8-XX:MetaspaceSize=<size>
:元空间初始大小-XX:MaxMetaspaceSize=<size>
:元空间最大大小(默认无限制)
1.2.2 非堆内存参数
-XX:PermSize=<size>
:永久代初始大小(JDK8及以上已移除,使用元空间)-XX:MaxPermSize=<size>
:永久代最大大小(JDK8及以上已移除)-Xss<size>
:每个线程的栈大小,默认1M
1.3 GC相关参数
1.3.1 垃圾收集器选择
-XX:+UseSerialGC
:使用Serial + Serial Old收集器组合-XX:+UseParNewGC
:使用ParNew + Serial Old收集器组合-XX:+UseConcMarkSweepGC
:使用ParNew + CMS + Serial Old收集器组合-XX:+UseParallelGC
:使用Parallel Scavenge + Parallel Old收集器组合-XX:+UseG1GC
:使用G1收集器
1.3.2 GC日志参数
-XX:+PrintGC
:打印简单GC日志-XX:+PrintGCDetails
:打印详细GC日志-XX:+PrintGCDateStamps
:打印GC发生的时间戳-Xloggc:<file>
:将GC日志输出到指定文件-XX:+HeapDumpOnOutOfMemoryError
:OOM时生成堆转储文件-XX:HeapDumpPath=<path>
:堆转储文件路径
1.3.3 GC调优参数
-XX:MaxGCPauseMillis=<n>
:G1收集器目标最大停顿时间-XX:GCTimeRatio=<n>
:Parallel Scavenge收集器吞吐量目标(1/(1+n))-XX:ParallelGCThreads=<n>
:并行GC线程数-XX:ConcGCThreads=<n>
:CMS或G1并发GC线程数
1.4 其他常用参数
-XX:+DisableExplicitGC
:禁用System.gc()-XX:+UseCompressedOops
:启用压缩普通对象指针(32位指针表示64位地址)-XX:+PrintCommandLineFlags
:打印JVM启动时的参数-D<name>=<value>
:设置系统属性
代码实践
2.1 查看JVM参数
2.1.1 查看JVM默认参数
java -XX:+PrintCommandLineFlags -version
2.1.2 查看进程JVM参数
# 查找Java进程ID
jps
# 查看指定进程的JVM参数
jinfo <pid>
2.2 设置JVM参数示例
2.2.1 基本堆内存配置
java -Xms512m -Xmx1024m -jar app.jar
2.2.2 G1收集器配置
java -Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintGCDetails -Xloggc:g1-gc.log -jar app.jar
2.2.3 生产环境常用配置
java -server -Xms4g -Xmx4g -Xmn1g -XX:SurvivorRatio=8 -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/var/log/app/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/app/heapdump.hprof -jar app.jar
2.3 分析JVM参数配置
使用jconsole或jvisualvm连接Java进程,查看内存配置是否符合预期:
- 启动jvisualvm
- 连接目标Java进程
- 在"监视"标签页查看堆内存使用情况
- 在"JVM参数"标签页查看当前JVM参数
设计思想
3.1 参数设计的权衡原则
JVM参数设计体现了多种权衡:
- 内存与性能:更大的堆内存减少GC频率,但增加单次GC时间
- 吞吐量与延迟:Parallel Scavenge关注吞吐量,CMS/G1关注延迟
- 默认值与自定义:JVM提供合理默认值,同时允许根据应用特性自定义
- 简单性与灵活性:标准参数简单易用,高级参数提供灵活调优
3.2 分代思想在参数中的体现
内存分代思想在JVM参数中得到充分体现:
- NewRatio和SurvivorRatio参数控制新生代与老年代比例
- 不同代有独立的GC参数和收集器选择
- 新生代大小直接影响Minor GC频率和耗时
3.3 自适应调优理念
Parallel Scavenge收集器引入自适应调优理念:
- 通过-XX:+UseAdaptiveSizePolicy启用
- JVM根据当前系统运行情况自动调整新生代大小、Eden与Survivor比例等参数
- 减少人工调优负担,适合对JVM调优不熟悉的场景
避坑指南
4.1 堆内存设置不当
- 内存过小:频繁GC,甚至OOM 解决:根据应用内存需求合理设置-Xms和-Xmx
- 内存过大:单次GC时间过长,浪费系统资源 解决:堆内存一般不超过物理内存的70%,大内存建议使用G1收集器
- Xms与Xmx不一致:导致堆内存动态调整,影响性能 解决:生产环境建议将Xms和Xmx设置为相同值
4.2 元空间溢出
- 原因:元空间大小未限制,类加载过多导致溢出
- 解决:设置-XX:MaxMetaspaceSize限制元空间大小,排查类加载泄漏
4.3 过度调优
- 问题:盲目调整大量参数,不仅无法提升性能,还可能引入新问题
- 解决:遵循"先监控,后调优"原则,只调整有明确优化目标的参数
4.4 忽略GC日志
- 问题:不开启GC日志,无法分析GC问题
- 解决:生产环境务必开启GC日志记录,包括详细信息和时间戳
4.5 错误使用废弃参数
- 问题:使用JDK版本中已废弃或移除的参数(如JDK8使用PermSize)
- 解决:查阅对应JDK版本的官方文档,确认参数可用性
深度思考题
- 为什么建议将-Xms和-Xmx设置为相同值?在什么情况下可能需要设置为不同值?
- 如何根据应用特点选择合适的垃圾收集器?
- 什么是JVM参数的"黄金比例"?如何确定适合特定应用的参数配置?
思考题回答:
将-Xms和-Xmx设置为相同值可以避免堆内存动态扩展,减少内存调整带来的性能开销。在以下情况可能需要设置为不同值:
- 应用启动初期内存需求小,后期需求增长
- 服务器内存资源紧张,需要多个应用共享内存
- 开发测试环境,希望节省内存资源
选择垃圾收集器应考虑:
- 应用类型:桌面应用可容忍较长停顿,优先选择Serial收集器;服务器应用关注吞吐量或延迟
- 堆内存大小:大堆内存(>4GB)优先选择G1
- 延迟要求:对响应时间敏感的应用选择CMS或G1
- 吞吐量要求:批处理应用选择Parallel Scavenge
- JDK版本:JDK9及以上G1为默认收集器,JDK17引入ZGC/Shenandoah等低延迟收集器
JVM参数的"黄金比例"是指根据应用特性找到的最优参数组合,没有统一标准。确定方法:
- 基准测试:建立性能基准,测量不同参数组合的性能指标
- 监控分析:收集生产环境GC日志、内存使用情况
- 逐步调整:一次只调整一个参数,观察影响
- 关注瓶颈:优先解决明显的性能瓶颈(如频繁Full GC)
- 考虑硬件:根据CPU核心数、内存大小调整并行线程数等参数