理解JVM调优的目标
- 性能提升:通过调整 JVM 参数,提高应用程序的运行速度和响应时间。例如,减少垃圾回收(GC)的停顿时间,从而让应用程序在处理高并发请求时能够更快速地响应,像电商平台在促销活动期间,大量用户并发访问商品详情和下单,优化 JVM 能减少响应延迟。
- 资源利用优化:合理利用系统的内存和 CPU 等资源。确保 JVM 不会过度占用内存导致系统其他进程受到影响,同时也避免频繁的内存回收操作浪费 CPU 资源。例如,在服务器上运行多个 Java 应用,优化每个应用的 JVM 内存配置,使服务器资源得到均衡分配。
堆内存调优
初始堆大小(-Xms)和最大堆大小(-Xmx)
参数含义:
-Xms指定 JVM 启动时分配的初始堆内存大小,在代码中用Runtime.getRuntime().totalMemory()来获取当前JVM已分配的内存大小(包含已使用和未被使用的内存,即freeMemory) ;-Xmx指定 JVM 堆内存的最大允许大小,代码中用Runtime.getRuntime().maxMemory()来获取。调优策略:
- 稳定内存占用应用:如果应用程序的内存需求比较稳定,例如简单的命令行工具或者小型的桌面应用,可将
-Xms和-Xmx设置为相同的值,这样可以避免 JVM 在运行过程中频繁调整堆内存大小带来的性能开销。例如,设置-Xms512m -Xmx512m。 - 内存波动应用:对于 Web 应用服务器这种内存占用会随着负载变化而波动的应用,应根据服务器硬件资源和预期负载合理设置
-Xmx,防止内存溢出。同时,适当设置-Xms,让应用在启动时有足够的内存。例如,在一台具有 8GB 内存的服务器上运行 Web 应用,可设置-Xms2048m -Xmx4096m。
- 稳定内存占用应用:如果应用程序的内存需求比较稳定,例如简单的命令行工具或者小型的桌面应用,可将
新生代(Young Generation)和老生代(Old Generation)比例调整
内存划分与作用:堆内存分为新生代和老生代。新生代用于存放新创建的对象,经过多次垃圾回收后存活的对象会移到老生代。通过
-XX:NewRatio参数调整新生代和老生代的比例。调优依据:
- 短生命周期对象为主的应用:如果应用大量创建短生命周期对象,如处理大量临时数据的计算任务,可适当增大新生代比例。比如设置
-XX:NewRatio = 1(新生代和老生代比例为 1:1),让更多对象在新生代被回收,减少对象晋升到老生代的频率,提高垃圾回收效率。 - 长生命周期对象为主的应用:对于对象生命周期较长的应用,如缓存系统,适当增大老生代比例,如
-XX:NewRatio = 3(新生代和老生代比例为 1:3)。
- 短生命周期对象为主的应用:如果应用大量创建短生命周期对象,如处理大量临时数据的计算任务,可适当增大新生代比例。比如设置
Eden 区和 Survivor 区大小设置
- 原理与默认设置:在新生代中,对象主要在 Eden 区创建。Eden 区满时触发 Minor GC,存活对象复制到 Survivor 区。
-XX:SurvivorRatio参数用于设置 Eden 区和 Survivor 区大小比例,默认 Eden 区占新生代 80%,两个 Survivor 区各占 10%。 - 调优场景:
- 短时间回收对象居多的应用:如果应用创建的对象大部分在短时间内就会被回收,适当增大 Eden 区比例,如设置
-XX:SurvivorRatio = 6(Eden 区占新生代的 6/7),减少 Minor GC 频率。 - Survivor 区溢出问题解决:若发现 Survivor 区频繁溢出,即对象在 Survivor 区还没来得及被回收就填满了 Survivor 区,可调整
-XX:SurvivorRatio参数或者增加堆内存大小。
- 短时间回收对象居多的应用:如果应用创建的对象大部分在短时间内就会被回收,适当增大 Eden 区比例,如设置
- 原理与默认设置:在新生代中,对象主要在 Eden 区创建。Eden 区满时触发 Minor GC,存活对象复制到 Survivor 区。
非堆内存调优
- 方法区(Metaspace)大小调整
- 功能与变化(JDK 8 之后):JDK 8 后,方法区被元空间(Metaspace)取代,使用本地内存。通过
-XX:MetaspaceSize和-XX:MaxMetaspaceSize参数分别设置元空间初始大小和最大大小。 - 调优要点:对于加载大量类或动态生成类较多的应用,如具有复杂插件系统的软件,要合理设置元空间大小。如果元空间过小,可能会出现
java.lang.OutOfMemoryError: Metaspace错误。可根据应用加载类的数量和大小历史数据或性能测试确定合适大小。
- 功能与变化(JDK 8 之后):JDK 8 后,方法区被元空间(Metaspace)取代,使用本地内存。通过
- 栈内存(Stack Memory)设置
- 栈的作用与参数含义:每个 Java 线程都有自己的栈,用于存储方法调用的局部变量等信息。
-Xss参数设置线程栈大小。 - 优化考虑因素:
- 避免栈溢出:栈大小设置要平衡内存占用和线程执行需求。若栈过小,可能导致栈溢出(
StackOverflowError),特别是在方法调用层次深或递归调用多的情况下。例如,对于递归算法较多的应用,适当增大-Xss的值。 - 内存资源利用:若栈设置过大,会浪费内存资源,尤其在创建大量线程的应用中。例如,在多线程服务器应用中,要根据实际线程行为和内存资源优化
-Xss参数。
- 避免栈溢出:栈大小设置要平衡内存占用和线程执行需求。若栈过小,可能导致栈溢出(
- 栈的作用与参数含义:每个 Java 线程都有自己的栈,用于存储方法调用的局部变量等信息。
垃圾回收(GC)策略优化
- 选择合适的垃圾回收器
- 不同垃圾回收器特点:
- Serial GC:单线程回收,适合小型应用,实现简单,停顿时间可能较长。
- Parallel GC:并行回收,适合多核服务器,能提高垃圾回收效率,适用于对吞吐量要求高的应用。
- CMS(Concurrent Mark - Sweep):并发标记清除,减少垃圾回收停顿时间,适合对响应时间要求高的应用。
- G1(Garbage - First):分区回收,兼顾高吞吐量和低停顿,能更灵活地处理内存回收。
- 优化依据:
- 实时性要求高的应用:对于实时交易系统这种对响应时间要求极高的应用,选择 CMS 或 G1 垃圾回收器,减少垃圾回收时的停顿时间。
- 批处理任务或高吞吐量应用:对于大数据处理任务等对内存吞吐量要求高的应用,选择 Parallel GC 比较合适。
- 垃圾回收参数调整
- 以 G1 垃圾回收器为例:
- 控制停顿时间:通过
-XX:MaxGCPauseMillis参数设置目标最大垃圾回收停顿时间。例如,设置-XX:MaxGCPauseMillis = 200,让垃圾回收停顿时间尽量不超过 200 毫秒。 - 控制回收时机:
-XX:InitiatingHeapOccupancyPercent参数控制当堆内存占用达到多少百分比时开始垃圾回收,合理设置可平衡回收频率和效率。
- 控制停顿时间:通过
- 以 G1 垃圾回收器为例:
- 不同垃圾回收器特点:
性能监控与调优工具的使用
- JDK 自带工具
- jstat 工具:用于实时监控 JVM 各种统计信息,如堆内存使用情况、垃圾回收次数和时间等。例如,
jstat -gcutil <pid>(<pid>是进程 ID)可查看进程的垃圾回收利用率,包括新生代、老生代使用比例和回收频率,帮助判断内存使用是否健康和回收是否正常。 - jmap 工具:用于生成堆内存快照。例如,
jmap -dump:format = b,file = heap.dump <pid>可将指定进程的堆内存信息以二进制格式保存到heap.dump文件,分析该文件可查看对象分布、是否存在内存泄漏等问题。 - jconsole 工具:提供图形化界面监控 JVM 性能,显示内存使用、线程状态、类加载信息等。通过它可直观看到 JVM 运行状态,及时发现异常。
- jstat 工具:用于实时监控 JVM 各种统计信息,如堆内存使用情况、垃圾回收次数和时间等。例如,
- 第三方性能监控工具(如 VisualVM 等)
- 功能优势:VisualVM 功能强大,可监控本地和远程 JVM。它集成多种插件,能进行更深入性能分析,如性能剖析(Profiling),查看方法执行时间和调用次数,帮助定位性能瓶颈。
- 使用场景:在开发和测试复杂 Java 应用时,使用 VisualVM 等工具可全面了解 JVM 性能状况。例如,优化大型 Web 应用时,通过 VisualVM 连接应用服务器的 JVM,对不同负载下的内存使用、线程性能等进行长时间监控和分析,为 JVM 配置优化提供数据支持。