理解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参数或者增加堆内存大小。

非堆内存调优

  • 方法区(Metaspace)大小调整
    • 功能与变化(JDK 8 之后):JDK 8 后,方法区被元空间(Metaspace)取代,使用本地内存。通过-XX:MetaspaceSize-XX:MaxMetaspaceSize参数分别设置元空间初始大小和最大大小。
    • 调优要点:对于加载大量类或动态生成类较多的应用,如具有复杂插件系统的软件,要合理设置元空间大小。如果元空间过小,可能会出现java.lang.OutOfMemoryError: Metaspace错误。可根据应用加载类的数量和大小历史数据或性能测试确定合适大小。
  • 栈内存(Stack Memory)设置
    • 栈的作用与参数含义:每个 Java 线程都有自己的栈,用于存储方法调用的局部变量等信息。-Xss参数设置线程栈大小。
    • 优化考虑因素
      • 避免栈溢出:栈大小设置要平衡内存占用和线程执行需求。若栈过小,可能导致栈溢出(StackOverflowError),特别是在方法调用层次深或递归调用多的情况下。例如,对于递归算法较多的应用,适当增大-Xss的值。
      • 内存资源利用:若栈设置过大,会浪费内存资源,尤其在创建大量线程的应用中。例如,在多线程服务器应用中,要根据实际线程行为和内存资源优化-Xss参数。

垃圾回收(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参数控制当堆内存占用达到多少百分比时开始垃圾回收,合理设置可平衡回收频率和效率。

性能监控与调优工具的使用

  • JDK 自带工具
    • jstat 工具:用于实时监控 JVM 各种统计信息,如堆内存使用情况、垃圾回收次数和时间等。例如,jstat -gcutil <pid><pid>是进程 ID)可查看进程的垃圾回收利用率,包括新生代、老生代使用比例和回收频率,帮助判断内存使用是否健康和回收是否正常。
    • jmap 工具:用于生成堆内存快照。例如,jmap -dump:format = b,file = heap.dump <pid>可将指定进程的堆内存信息以二进制格式保存到heap.dump文件,分析该文件可查看对象分布、是否存在内存泄漏等问题。
    • jconsole 工具:提供图形化界面监控 JVM 性能,显示内存使用、线程状态、类加载信息等。通过它可直观看到 JVM 运行状态,及时发现异常。
  • 第三方性能监控工具(如 VisualVM 等)
    • 功能优势:VisualVM 功能强大,可监控本地和远程 JVM。它集成多种插件,能进行更深入性能分析,如性能剖析(Profiling),查看方法执行时间和调用次数,帮助定位性能瓶颈。
    • 使用场景:在开发和测试复杂 Java 应用时,使用 VisualVM 等工具可全面了解 JVM 性能状况。例如,优化大型 Web 应用时,通过 VisualVM 连接应用服务器的 JVM,对不同负载下的内存使用、线程性能等进行长时间监控和分析,为 JVM 配置优化提供数据支持。