深入解读JVM调优、原理、实践与最佳实践

1. 引言

JVM 的重要性

Java 虚拟机(JVM)是运行 Java 程序的核心组件,它充当了 Java 程序与底层操作系统之间的桥梁。JVM 负责将编译后的 Java 字节码(Bytecode)解释或编译为与特定平台相关的机器码,使得 Java 程序具有“一次编写,到处运行”的特性。

JVM 提供了诸如内存管理、垃圾回收(GC)、线程调度和安全机制等关键功能。这些功能确保了应用程序的稳定性和性能,使开发者能够专注于业务逻辑,而无需过多关注底层细节。因此,理解 JVM 的工作原理对于开发高性能、高可靠性的 Java 应用程序至关重要。

调优的必要性

默认的 JVM 配置可能无法满足所有应用程序的性能需求,尤其是在高并发、大数据处理和低延迟要求的场景中。调优 JVM 可以带来以下好处:

  • 提高性能:优化内存分配和垃圾回收策略,减少停顿时间,提高应用的吞吐量。
  • 稳定性:避免内存泄漏和溢出,确保应用长时间稳定运行。
  • 资源利用率:更有效地利用 CPU 和内存资源,降低运营成本。
  • 满足 SLA:通过优化,满足服务水平协议中对响应时间和可用性的要求。

因此,掌握 JVM 调优技巧是 Java 开发者和运维工程师提升系统性能和稳定性的必备技能。

2. JVM 内存结构

JVM 在运行时会将所管理的内存划分为若干区域,每个区域都有特定的用途。了解这些区域有助于我们更好地进行内存管理和调优。

方法区(Method Area)

方法区是线程共享的内存区域,用于存储已被加载的类信息、常量、静态变量、即时编译器(JIT)编译后的代码等数据。在 JDK 8 之前,方法区也被称为永久代(PermGen)。从 JDK 8 开始,永久代被移除,取而代之的是元空间(Metaspace),它使用本地内存而不是堆内存。

  • 特点
    • 存储类元数据,如类名、访问修饰符、常量池等。
    • 由于存储的是类的信息,方法区的大小一般不需要太大,但需要根据应用中类的数量进行调整。

堆(Heap)

堆是 JVM 管理的最大一块内存空间,几乎所有的对象实例和数组都在这里分配内存。堆是垃圾收集器管理的主要区域,因此也被称为 GC 堆。

堆被划分为以下两个代:

  • 年轻代(Young Generation)

    年轻代用于存储新创建的对象。由于大部分对象都是短命的,因此年轻代的垃圾回收会比较频繁。年轻代又被细分为以下三个区域:

    • Eden 区

      Eden 区是新对象分配内存的主要区域。当 Eden 区被填满时,会触发一次年轻代垃圾回收(Minor GC),将存活的对象移动到 Survivor 区。

    • From Survivor 区To Survivor 区

      两个 Survivor 区用于在垃圾回收过程中复制和交换存活的对象。每次垃圾回收后,From 和 To 区的角色都会互换。

  • 老年代(Old Generation)

    老年代用于存储在年轻代经过多次垃圾回收仍然存活的对象,以及大对象(无法放入年轻代的对象)。老年代的垃圾回收频率较低,但每次回收耗时较长。

栈(Stack)

JVM 栈是线程私有的,生命周期与线程相同。每个方法在执行时都会创建一个栈帧,用于存储局部变量、操作数栈、动态链接和方法返回地址等信息。

  • 特点
    • 栈中的局部变量表用于存储基本数据类型和对象引用。
    • 由于栈是线程私有的,因此不需要考虑线程安全问题。

本地方法栈(Native Method Stack)

本地方法栈与 JVM 栈类似,只不过它为虚拟机使用到的本地(Native)方法服务。当 Java 程序调用本地方法时,会在本地方法栈中执行。

  • 特点
    • 主要用于支持使用 C/C++ 等其他语言编写的本地方法。
    • 如果本地方法栈出现溢出,会抛出 StackOverflowError

程序计数器(Program Counter Register)

程序计数器是一块较小的内存空间,用于记录当前线程所执行的字节码指令地址。由于 JVM 的多线程是通过线程轮流切换并分配处理器时间来实现的,因此每个线程都需要一个独立的程序计数器,以在线程切换后能恢复到正确的执行位置。

  • 特点
    • 如果线程正在执行的是本地方法,程序计数器的值则为未定义(Undefined)。
    • 这是 JVM 唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。

3. 垃圾回收机制(GC)

垃圾回收的概念

垃圾回收(Garbage Collection,GC)是 JVM 自动内存管理的重要机制。它的主要作用是:

  • 自动释放内存:无需开发者手动管理内存分配和释放,降低了内存泄漏和指针错误的风险。
  • 优化内存使用:通过回收不再使用的对象,腾出内存空间供新对象使用。
  • 提高应用稳定性:避免内存不足导致的程序崩溃。

GC 的核心理念是发现并回收那些不再被引用的对象,即垃圾对象。JVM 中的垃圾回收机制负责跟踪对象的引用关系,确定哪些对象是可达的(仍然被使用),哪些是不可达的(可被回收)。

垃圾回收算法

为了高效地回收垃圾对象,JVM 实现了多种垃圾回收算法。主要的算法有:

  1. 标记-清除算法(Mark-Sweep)

    • 工作原理:首先,从根集合(如栈、寄存器中的引用)出发,遍历并标记所有可达的对象;然后,扫描堆中的所有对象,清除未被标记的对象(即不可达对象)。
    • 优点
      • 简单,易于实现。
      • 不需要移动对象,适用于对象生命周期较长的场景。
    • 缺点
      • 会产生大量的内存碎片,导致内存空间不连续。
      • 清除阶段的效率较低。
  2. 复制算法(Copying)

    • 工作原理:将内存划分为大小相等的两块,每次只使用其中一块。当该块内存耗尽时,将仍然存活的对象复制到另一块,然后清空当前使用的内存区域。
    • 优点
      • 分配内存时无需考虑内存碎片,内存分配效率高。
      • 清理过程简单,只需复制存活对象。
    • 缺点
      • 需要两倍的内存空间,内存利用率低。
      • 不适合老年代,大多数对象都存活时,复制成本高。
  3. 标记-整理算法(Mark-Compact)

    • 工作原理:首先标记所有可达的对象,然后将所有存活的对象向一端移动,最后清理边界以外的内存。
    • 优点
      • 消除了内存碎片,内存空间更加连续。
      • 适用于老年代,对象存活率高的情况。
    • 缺点
      • 移动对象需要更新引用,成本较高。
      • 整理过程可能会导致较长的停顿时间。

垃圾回收器

JVM 提供了多种垃圾回收器,不同的收集器实现了不同的垃圾回收算法,适用于不同的应用场景。

  1. Serial 收集器

    • 特点
      • 单线程垃圾收集器,垃圾回收时会暂停所有应用线程(Stop-The-World,STW)。
      • 使用复制算法用于年轻代,标记-整理算法用于老年代。
    • 适用场景
      • 适用于单核或小内存环境,如客户端应用程序。
      • 简单高效,适合不要求高并发的场景。
  2. Parallel 收集器

    • 特点
      • 多线程垃圾收集器,使用多个线程并行进行垃圾回收。
      • 年轻代使用并行复制算法,老年代使用并行标记-整理算法。
    • 适用场景
      • 追求高吞吐量的应用,如后台计算、批处理等。
      • 能够充分利用多核 CPU,提高垃圾回收效率。
  3. CMS(Concurrent Mark Sweep)收集器

    • 特点

      • 主要针对老年代的收集器,目标是获取最短回收停顿时间。
      • 使用标记-清除算法,分为初始标记、并发标记、重新标记、并发清除四个步骤。
      • 大部分工作与应用线程并发执行,降低停顿时间。
    • 适用场景

      • 适用于对响应时间要求高的应用,如 Web 服务器。
      • 需要较大的内存空间来支持并发操作。
    • 缺点

      • 会产生内存碎片,可能导致 Full GC。
      • 并发模式失败(Concurrent Mode Failure):如果在垃圾回收期间内存不足,需要重新触发一次完全的 Stop-The-World 回收。
  4. G1(Garbage First)收集器

    • 特点

      • 面向服务器端应用,旨在替代 CMS 收集器。
      • 将堆内存划分为多个大小相等的独立区域(Region),不再区分年轻代和老年代的物理隔离。
      • 使用全新的回收算法,兼顾吞吐量和停顿时间。
      • 支持可预测的停顿时间模型,可指定停顿时间目标。
    • 适用场景

      • 大内存、多核 CPU 的服务器应用。
      • 需要在高吞吐量和低停顿时间之间取得平衡的场景。
    • 工作流程

      • 初始标记:标记从根节点直接可达的对象,短暂的 STW。
      • 并发标记:从可达对象开始进行图的遍历,找出存活对象,和应用程序并发执行。
      • 最终标记:处理并发标记期间产生的少量引用变动,短暂的 STW。
      • 筛选回收:根据停顿时间目标,优先回收收益最大的区域。
  5. ZGC 和 Shenandoah

    • ZGC(Z Garbage Collector)

      • 特点
        • 低延迟垃圾收集器,旨在将 GC 停顿时间控制在 10ms 以内。
        • 可以处理非常大的堆内存(TB 级别)。
        • 使用着色指针(Colored Pointers)和读屏障(Read Barriers)技术,实现并发的标记和整理。
      • 适用场景
        • 超大内存、对延迟敏感的应用程序。
    • Shenandoah

      • 特点
        • 由 RedHat 开发的低延迟垃圾收集器。
        • 与 ZGC 类似,采用并发的标记和整理阶段。
        • 使用 Brooks Pointer 和读写屏障技术。
      • 适用场景
        • 需要大内存支持且对停顿时间有严格要求的应用。
    • 共同点

      • 都是为了解决在大堆内存下,GC 停顿时间过长的问题。
      • 通过更多的并发操作,减少 STW 的时间。

选择合适的垃圾回收器和算法,需要根据应用程序的特点和性能要求进行权衡。一般来说:

  • 如果追求简单和单线程性能,选择 Serial 收集器
  • 如果需要高吞吐量,且对停顿时间要求不高,选择 Parallel 收集器
  • 如果需要较短的停顿时间,对响应时间敏感,选择 CMS 收集器G1 收集器
  • 对于大内存、低停顿需求的应用,可以考虑 ZGCShenandoah

4. JVM 参数调优

在了解了 JVM 的内存结构和垃圾回收机制后,我们可以通过调整 JVM 的参数来优化 Java 应用程序的性能。合理的参数设置能够有效提高资源利用率,减少垃圾回收带来的停顿时间,提升系统的稳定性和响应速度。

内存设置

内存设置是 JVM 调优的基础,主要包括堆内存和年轻代内存的配置。

  • 堆内存大小:-Xms-Xmx

    • -Xms:设置 JVM 初始化时的堆内存大小(最小堆内存)。
    • -Xmx:设置 JVM 允许分配的最大堆内存大小。

    调优建议

    • 设置相同的初始和最大堆内存:为了避免堆内存的动态扩展和收缩带来的性能开销,建议将 -Xms-Xmx 设置为相同的值。
    • 根据物理内存合理分配:堆内存不宜设置过大或过小。过大会导致系统可用内存不足,过小会导致频繁的垃圾回收甚至 OutOfMemoryError。一般建议堆内存大小不超过物理内存的 80%。
  • 年轻代大小:-Xmn-XX:NewSize-XX:MaxNewSize

    • -Xmn:直接设置年轻代(Young Generation)的大小。
    • -XX:NewSize:设置年轻代的初始大小。
    • -XX:MaxNewSize:设置年轻代的最大大小。

    调优建议

    • 适当增大年轻代:如果应用程序中短生命周期对象较多,增大年轻代的大小可以减少对象晋升到老年代的频率,从而降低老年代的垃圾回收压力。
    • 年轻代与堆内存的比例:通常将年轻代大小设置为整个堆内存的 1/3 或 1/4,但具体数值需要根据应用的特点和测试结果进行调整。

垃圾回收器选择

不同的垃圾回收器适用于不同的应用场景。通过指定 JVM 参数,可以选择最合适的垃圾回收器。

  • -XX:+UseSerialGC

    • 说明:启用串行垃圾回收器,年轻代和老年代都使用单线程进行垃圾回收。
    • 适用场景:适用于单核处理器和小型应用程序,对响应时间要求不高。
  • -XX:+UseParallelGC

    • 说明:启用并行垃圾回收器,年轻代使用多线程进行垃圾回收,老年代使用单线程。
    • 适用场景:注重吞吐量的多核服务器应用,对停顿时间要求不高。
  • -XX:+UseG1GC

    • 说明:启用 G1 垃圾回收器,适用于大堆内存和多核 CPU 的服务器应用。
    • 适用场景:需要在较短停顿时间内管理大内存的应用程序,兼顾吞吐量和响应时间。

元空间(Metaspace)设置

从 JDK 8 开始,JVM 将类的元数据从堆内存中移出,放入到本地内存的元空间(Metaspace)中。

  • -XX:MetaspaceSize

    • 说明:设置元空间的初始大小。当元空间的使用达到该值时,JVM 将触发 Full GC,并可能卸载一些不再使用的类。
  • -XX:MaxMetaspaceSize

    • 说明:设置元空间的最大大小。默认情况下,元空间的大小仅受限于系统可用内存。

    调优建议

    • 预估元空间大小:对于大量动态生成类的应用,如使用 CGLIB、动态代理或大量 JSP 的应用,应该适当增大元空间的大小,防止出现 OutOfMemoryError: Metaspace 错误。
    • 监控元空间使用:使用监控工具观察元空间的使用情况,及时调整参数。

GC 日志

启用 GC 日志有助于分析垃圾回收行为,发现性能瓶颈。

  • 启用 GC 日志:-XX:+PrintGC-XX:+PrintGCDetails

    • -XX:+PrintGC:在每次垃圾回收时打印简要的信息,包括垃圾回收前后堆内存的使用情况。
    • -XX:+PrintGCDetails:打印更详细的垃圾回收信息,包括各代内存的使用、回收时间等。
  • 输出 GC 日志到文件:-Xloggc:<filename>

    • 说明:将 GC 日志信息输出到指定的文件中,便于后续分析。
    • 示例-Xloggc:gc.log 将 GC 日志输出到当前目录下的 gc.log 文件。

    其他常用参数

    • -XX:+PrintGCTimeStamps:在 GC 日志中添加时间戳。
    • -XX:+PrintGCDateStamps:在 GC 日志中添加日期和时间。
    • -XX:+PrintTenuringDistribution:打印对象在各年龄段的分布信息。

    调优建议

    • 定期分析 GC 日志:通过分析 GC 日志,了解垃圾回收的频率和停顿时间,判断内存配置和垃圾回收器的选择是否合理。
    • 使用日志分析工具:借助如 GCViewer、GCEasy 等工具,可以对 GC 日志进行可视化分析,更直观地发现问题。

示例:综合 JVM 参数配置

假设我们有一个高并发的服务器应用,需要平衡吞吐量和响应时间,对垃圾回收的停顿时间有一定要求。

java -Xms4g -Xmx4g -Xmn2g -XX:+UseG1GC -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/var/log/gc.log -jar myapp.jar
  • -Xms4g -Xmx4g:将堆内存大小固定为 4GB,避免动态扩展带来的性能开销。
  • -Xmn2g:将年轻代大小设置为 2GB,占堆内存的一半,适合短生命周期对象较多的应用。
  • -XX:+UseG1GC:选择 G1 垃圾回收器,适用于大内存、低停顿时间的需求。
  • -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m:设置元空间的初始和最大大小,防止类元数据过多导致内存不足。
  • GC 日志参数
    • -XX:+PrintGCDetails -XX:+PrintGCDateStamps:启用详细的 GC 日志并添加日期时间戳。
    • -Xloggc:/var/log/gc.log:将 GC 日志输出到指定的日志文件。

注意事项

  1. 参数设置需要测试验证:不同的应用有不同的特性,以上参数仅供参考。实际调优中,需要根据应用的运行情况和性能测试结果,逐步调整参数。

  2. 关注物理资源限制:在设置 JVM 内存参数时,要考虑服务器的物理内存和其他应用的内存需求,避免因为 JVM 占用过多内存导致系统不稳定。

  3. 持续监控和调整:应用上线后,仍需持续监控 JVM 的内存使用和垃圾回收情况,及时发现并解决潜在的问题。

5. 性能监控和调优工具

在 JVM 调优过程中,性能监控和分析工具起着至关重要的作用。它们帮助我们了解 JVM 的运行状态、内存使用情况、垃圾回收行为等,从而定位性能瓶颈,指导调优策略的制定。根据使用方式的不同,这些工具可以分为命令行工具、可视化工具和第三方工具。

命令行工具

命令行工具是 JDK 提供的一组实用程序,适用于实时监控和诊断 Java 应用程序。这些工具可以在生产环境中使用,通常对系统性能影响较小。

1. jps(Java Virtual Machine Process Status Tool)
  • 功能

    • 列出当前运行的 Java 进程及其进程 ID(PID)。
    • 显示 Java 应用程序启动时指定的主类名或 Jar 文件名。
  • 用法

    jps [options] [hostid]
    
  • 常用选项

    • -l:输出主类的全限定名或 Jar 文件的完整路径。
    • -v:输出传递给 JVM 的参数。
  • 示例

    jps -lvm
    

    输出包括进程 ID、主类或 Jar 文件、JVM 参数等详细信息。

2. jstat(JVM Statistics Monitoring Tool)
  • 功能

    • 监视 JVM 的内存和垃圾回收状态。
    • 提供堆内存、类加载、垃圾回收等多种统计信息。
  • 用法

    jstat [options] <vmid> [interval] [count]
    
  • 常用选项

    • -gc:显示垃圾收集相关的堆信息。
    • -gcutil:显示垃圾收集的时间和堆使用率。
  • 示例

    jstat -gcutil 12345 1000 10
    

    对进程 ID 为 12345 的 JVM,每隔 1000 毫秒输出一次垃圾回收统计信息,共输出 10 次。

3. jmap(Memory Map)
  • 功能

    • 生成堆转储(Heap Dump)文件,用于后续的内存分析。
    • 显示堆内存的详细信息,如各个代的使用情况、对象数量等。
  • 用法

    jmap [options] <pid>
    
  • 常用选项

    • -heap:显示堆的概要信息。
    • -dump:format=b,file=<filename>:将堆内存转储到指定文件。
  • 示例

    jmap -dump:format=b,file=heapdump.hprof 12345
    

    将进程 ID 为 12345 的 JVM 的堆内存转储到 heapdump.hprof 文件。

4. jstack(Stack Trace)
  • 功能

    • 打印 JVM 中所有线程的堆栈信息。
    • 用于分析线程状态、死锁和性能瓶颈。
  • 用法

    jstack [options] <pid>
    
  • 常用选项

    • -l:打印关于锁的附加信息。
    • -m:同时打印 Java 和本地 C/C++ 代码的堆栈信息。
  • 示例

    jstack -l 12345 > thread_dump.txt
    

    将进程 ID 为 12345 的线程堆栈信息输出到 thread_dump.txt 文件。

可视化工具

可视化工具提供了图形界面,能够更直观地监控和分析 JVM 的运行状态。这些工具适用于需要深入分析的场景,可以实时显示内存使用、线程活动等信息。

1. JConsole(Java Monitoring and Management Console)
  • 功能

    • 基于 JMX(Java Management Extensions)技术,提供对 JVM 的监控和管理。
    • 实时显示内存、线程、类加载器和 CPU 使用情况。
    • 支持查看和修改 MBean(管理 Bean)的属性。
  • 使用方法

    • 直接在命令行输入 jconsole 启动。
    • 选择要监控的本地或远程 JVM 进程。
  • 特点

    • 界面简洁,易于使用。
    • 适合对应用程序进行基本的性能监控。
2. VisualVM
  • 功能

    • 集成了多个 JDK 工具的功能,如 jstatjstackjmap 等。
    • 提供内存分析、CPU 分析、线程分析等功能。
    • 支持插件扩展,提供更丰富的功能,如 GC 查看器、MBean 浏览器等。
  • 使用方法

    • 在 JDK 的 bin 目录下找到 jvisualvm,运行即可启动。
    • 自动列出本地运行的 JVM 进程,选择要分析的进程。
  • 特点

    • 界面友好,功能强大。
    • 可以生成并分析堆转储,查找内存泄漏和性能瓶颈。
3. Java Mission Control(JMC)
  • 功能

    • 低开销的性能监控和分析工具。
    • 与 Java Flight Recorder(JFR)集成,能够记录 JVM 的运行数据,进行深入分析。
    • 提供实时的监控仪表盘,显示内存、线程、CPU 等关键指标。
  • 使用方法

    • 在 JDK 的 bin 目录下找到 jmc,运行即可启动。
    • 连接到目标 JVM,开始监控和数据收集。
  • 特点

    • 适用于生产环境,性能开销非常小。
    • 能够捕获细粒度的运行时数据,支持离线分析。

第三方工具

除了 JDK 自带的工具,还有一些功能更为强大的第三方工具,可用于深入的性能分析和问题定位。

1. MAT(Memory Analyzer Tool)
  • 功能

    • Eclipse 提供的内存分析工具,用于分析堆转储文件。
    • 能够查找内存泄漏、分析对象引用关系、计算对象大小等。
    • 提供高级查询功能,使用 OQL(Object Query Language)进行自定义查询。
  • 使用方法

    • 下载并安装 MAT,启动后打开堆转储文件(通常为 .hprof 格式)。
    • 使用内置的报告和分析功能,查找内存使用问题。
  • 特点

    • 功能强大,适用于大型堆转储的分析。
    • 界面友好,提供详细的分析报告。
2. GCViewer
  • 功能

    • 专用于分析 GC 日志的工具。
    • 将 GC 日志数据可视化,显示垃圾回收的频率、停顿时间、内存使用等指标。
    • 帮助评估垃圾回收器的性能,指导参数调整。
  • 使用方法

    • 下载并启动 GCViewer。
    • 导入 GC 日志文件(通过 -Xloggc 参数生成)。
    • 查看并分析各项指标。
  • 特点

    • 界面简洁,专注于垃圾回收的分析。
    • 能够直观地展示垃圾回收行为,辅助调优。

工具使用的最佳实践

  1. 在测试和生产环境中监控

    • 在测试环境中使用工具进行性能测试和调优,预先发现问题。
    • 在生产环境中持续监控,及时捕获异常情况。
  2. 合理选择工具

    • 命令行工具:对系统影响小,适合在生产环境中使用。
    • 可视化工具:提供更详细的信息,适合在测试和预生产环境中使用。
    • 第三方工具:功能强大,但可能对系统有一定影响,应谨慎使用。
  3. 定期分析和报告

    • 定期收集和分析性能数据,建立性能基线。
    • 生成报告,记录调优过程和结果,积累经验。
  4. 结合多种工具

    • 不同工具有不同的侧重点,结合使用可以全面了解系统性能。
    • 例如,使用 jstat 监控内存使用,使用 MAT 分析内存泄漏,使用 GCViewer 分析垃圾回收。

案例:使用工具定位性能问题

场景:某 Java 应用在运行一段时间后,出现了响应变慢和内存溢出的情况。

解决步骤

  1. 使用 jstat 监控内存和 GC 状态

    • 发现老年代内存持续增长,Full GC 频繁,停顿时间长。
  2. 使用 jmap 生成堆转储

    • 执行 jmap -dump:format=b,file=heapdump.hprof <pid>,生成堆转储文件。
  3. 使用 MAT 分析堆转储

    • 打开 heapdump.hprof 文件,查看对象的内存占用情况。
    • 发现某个集合对象占用了大量内存,疑似存在内存泄漏。
  4. 定位代码问题

    • 根据 MAT 提供的对象引用路径,找到代码中未正确释放资源的部分。
    • 修复代码,避免对象长期持有引用。
  5. 使用 GCViewer 分析 GC 日志

    • 查看垃圾回收的频率和停顿时间,验证优化效果。
  6. 持续监控

    • 使用 JConsole 或 VisualVM 观察优化后的运行情况,确保问题得到解决。

6. 调优实践

在了解了 JVM 的内存结构、垃圾回收机制和参数配置后,接下来就是实际的调优过程。调优实践主要分为以下几个步骤:定位性能瓶颈调整参数验证效果。通过系统性的调优,可以显著提升 Java 应用程序的性能和稳定性。

定位性能瓶颈

在开始调优之前,首先需要确定系统存在的性能问题和瓶颈。这一步至关重要,因为盲目地调整参数可能无法解决问题,甚至可能引入新的问题。

监控内存使用
  • 监控堆内存的使用情况

    • 使用 jstatVisualVMJConsole 等工具,实时监控堆内存的使用,包括年轻代和老年代的内存占用。
    • 观察内存占用趋势:注意内存使用是否持续增长,是否存在内存泄漏的迹象。
    • 关注垃圾回收后的内存回收率:如果每次垃圾回收后内存占用没有明显下降,可能存在长生命周期对象或内存泄漏。
  • 监控非堆内存的使用情况

    • 元空间(Metaspace):使用 jstat -classVisualVM,监控加载的类数量和元空间使用情况。
    • 直接内存(Direct Memory):如果应用大量使用 NIO 或 Netty,需要关注直接内存的使用,避免出现 OutOfMemoryError
分析 GC 行为
  • 收集 GC 日志

    • 在 JVM 启动参数中添加 -XX:+PrintGCDetails-XX:+PrintGCDateStamps-Xloggc:<filename> 等参数,收集完整的垃圾回收日志。
  • 使用 GC 日志分析工具

    • 借助 GCViewerGCEasy 等工具,对 GC 日志进行分析。
    • 关注指标
      • GC 频率:年轻代 GC(Minor GC)和老年代 GC(Full GC)的频率是否过高。
      • GC 停顿时间:每次 GC 导致的应用线程停顿时间是否在可接受范围内。
      • 内存回收效果:每次 GC 回收的内存量,GC 后各代内存的占用情况。
  • 观察 GC 的类型和原因

    • 频繁的 Full GC:可能由于老年代空间不足、元空间溢出等原因引起,需要重点关注。
    • Concurrent Mode Failure:使用 CMS 收集器时,如果并发回收未能及时完成,会触发 Full GC,需要调整参数或切换收集器。

调整参数

在定位了性能瓶颈后,可以针对性地调整 JVM 参数,以优化内存管理和垃圾回收行为。

优化堆和年轻代大小
  • 增大堆内存

    • 如果内存不足导致频繁的垃圾回收或 OutOfMemoryError,可以适当增大堆内存大小(-Xms-Xmx)。
    • 注意物理内存限制:确保堆内存大小不超过物理内存的 80%,避免系统内存不足。
  • 调整年轻代大小

    • 增大年轻代:对于短生命周期对象较多的应用,增大年轻代大小(-Xmn-XX:NewSize)可以减少对象进入老年代的频率,降低老年代的压力。
    • 减小年轻代:如果年轻代过大,可能导致 Minor GC 时间过长,可以适当减小年轻代大小。
  • 设置 Survivor 区比例

    • 使用 -XX:SurvivorRatio 参数调整 Eden 区与 Survivor 区的大小比例。
    • 根据对象的生命周期特征,调整该参数以优化 Survivor 区的利用率。
调整 GC 算法
  • 选择合适的垃圾回收器

    • 吞吐量优先:如果应用更关注吞吐量,且对停顿时间要求不高,可以选择 Parallel GC-XX:+UseParallelGC)。
    • 响应时间优先:如果应用对响应时间要求高,需要尽可能降低停顿时间,可以选择 G1 GC-XX:+UseG1GC)。
    • 低延迟需求:对于超低延迟的应用,可以考虑使用 ZGC-XX:+UseZGC,JDK 11 及以上)或 Shenandoah-XX:+UseShenandoahGC,需特定版本支持)。
  • 调整 GC 参数

    • G1 收集器参数
      • -XX:MaxGCPauseMillis:设置期望的最大 GC 停顿时间(毫秒)。
      • -XX:InitiatingHeapOccupancyPercent:设置触发并发 GC 周期的堆占用率阈值。
    • Parallel 收集器参数
      • -XX:ParallelGCThreads:设置用于垃圾回收的线程数。
      • -XX:MaxGCPauseMillis:设置目标最大停顿时间。

验证效果

调整参数后,需要验证调优是否达到了预期的效果。这通常需要通过压力测试和持续监控来完成。

压力测试
  • 模拟真实负载

    • 使用 JMeterLoadRunner 等压力测试工具,模拟实际的业务场景和并发请求。
    • 设定合理的测试指标:包括吞吐量、响应时间、错误率等。
  • 监控系统性能

    • CPU、内存、网络和磁盘 IO:使用系统监控工具或第三方监控平台(如 PrometheusGrafana)监控资源使用情况。
    • JVM 指标:使用 JConsoleVisualVMJava Mission Control 等工具,监控堆内存、GC 活动、线程数等。
  • 收集测试数据

    • 响应时间分布:观察 95%、99% 响应时间是否满足预期。
    • GC 停顿时间:确认是否在可接受的范围内。
持续监控
  • 部署监控系统

    • 在生产环境中部署持续监控和告警系统,实时监测应用的性能指标。
    • 关键指标
      • JVM 内存使用:堆、元空间、直接内存等占用情况。
      • GC 行为:GC 次数、停顿时间、Full GC 发生频率等。
      • 线程和连接数:活跃线程数、数据库连接数、HTTP 连接数等。
  • 日志分析

    • GC 日志:定期分析 GC 日志,了解垃圾回收的频率和效率,及时发现异常情况。
    • 应用日志:关注异常和错误信息,结合业务日志定位潜在的问题。
  • 预警和自动化处理

    • 设置告警阈值:当某些关键指标超出预设阈值时,触发告警通知相关人员。
    • 自动扩容或重启:结合容器化和微服务架构,自动扩展服务实例或重启异常服务,确保系统的高可用性。

案例分析:高并发应用的 JVM 调优实践

背景

一家电商网站在促销活动期间,访问量激增,服务器出现响应变慢、请求超时的情况。

问题定位

  • 监控内存使用:使用 VisualVM 发现堆内存占用率持续攀升,老年代使用率接近 90%。
  • 分析 GC 行为:通过 GC 日志 发现 Full GC 频繁发生,停顿时间长达几秒钟。

调优过程

  1. 调整堆和年轻代大小

    • 增大堆内存:将堆内存从 4GB 增加到 8GB-Xms8g -Xmx8g)。
    • 调整年轻代大小:将年轻代从 1GB 增加到 2GB-Xmn2g),以容纳更多新生代对象。
  2. 切换垃圾收集器

    • Parallel GC 切换到 G1 GC-XX:+UseG1GC),以降低停顿时间。
    • 设置最大停顿时间目标(-XX:MaxGCPauseMillis=200),希望 GC 停顿不超过 200 毫秒。
  3. 优化 G1 参数

    • -XX:InitiatingHeapOccupancyPercent=45:将并发标记周期的触发阈值降低到 45%,使 G1 更早地开始回收。
    • -XX:G1ReservePercent=15:增加保留内存,以应对突发的内存分配需求。

验证效果

  • 压力测试

    • 使用 JMeter 模拟高并发请求,观察到响应时间明显改善,平均响应时间从 500ms 降低到 200ms。
    • Full GC 频率大幅降低,停顿时间缩短到 200ms 以内。
  • 持续监控

    • 在生产环境中持续监控,系统在高负载下保持稳定,未再出现响应超时的情况。
    • 堆内存使用趋于平稳,老年代使用率维持在 60% 左右。

总结

通过对堆内存和年轻代大小的优化,以及切换到适合高并发场景的 G1 GC,成功解决了由于垃圾回收引起的性能问题,提高了系统的吞吐量和响应速度。

调优实践的关键要点

  • 深入了解应用特性:不同的应用有不同的内存和垃圾回收需求,调优时要结合具体的业务场景。
  • 循序渐进地调整参数:每次只调整一两个参数,观察其影响,避免一次性修改过多参数导致问题难以定位。
  • 重视验证和监控:调整参数后,必须通过压力测试和持续监控来验证效果,确保调优的有效性。
  • 积累经验和数据:记录调优的过程和结果,为后续的性能优化提供参考。

7. 最佳实践

在进行 JVM 调优时,遵循一些最佳实践可以帮助我们更有效地提升应用程序的性能和稳定性。以下是一些关键的建议:

合理设置堆大小

重要性

  • 避免内存不足:堆内存过小会导致频繁的垃圾回收,甚至触发 OutOfMemoryError,影响应用的正常运行。
  • 资源合理利用:堆内存过大会占用系统资源,可能导致操作系统的内存压力,影响其他应用的性能。

实践建议

  1. 根据应用需求确定堆大小

    • 性能测试:在测试环境中,通过负载测试确定应用的内存需求。
    • 监控内存使用:使用监控工具观察应用在峰值负载下的内存占用情况。
  2. 设置初始和最大堆大小相同

    • 使用参数 -Xms-Xmx 设置堆的初始和最大大小,建议将两者设置为相同的值,避免堆的动态扩展带来的性能开销。
  3. 考虑物理内存和系统环境

    • 保留足够的物理内存:确保堆内存大小不超过物理内存的 80%,为操作系统和其他应用留出空间。
    • 多 JVM 实例:在一台服务器上运行多个 JVM 时,要合理分配内存,避免总内存超出物理限制。
  4. 持续监控和调整

    • 监控内存使用趋势:观察内存使用的增长情况,及时调整堆大小。
    • 定期评估:随着应用功能的增加和负载的变化,定期评估并调整堆内存配置。

选择合适的 GC 算法

重要性

  • 影响应用性能:不同的垃圾收集器有不同的特性,对吞吐量和停顿时间的影响不同。
  • 适应应用场景:根据应用的特点选择合适的垃圾收集器,可以更好地满足性能需求。

实践建议

  1. 了解各垃圾收集器的特点

    • Serial GC:单线程,适用于小型应用或客户端程序。
    • Parallel GC:多线程,高吞吐量,适用于后台批处理等不敏感停顿时间的应用。
    • CMS GC:低停顿时间,适用于响应时间敏感的服务器应用(注意 CMS 已在 JDK 14 中被标记为过时)。
    • G1 GC:平衡吞吐量和停顿时间,适用于大内存、多核服务器应用。
    • ZGC 和 Shenandoah:超低停顿时间,适用于对延迟极度敏感的应用。
  2. 根据应用需求选择

    • 高吞吐量:选择 Parallel GC,可最大化 CPU 利用率。
    • 低停顿时间:选择 G1 GCZGCShenandoah,减少垃圾回收对响应时间的影响。
  3. 测试和验证

    • 性能测试:在测试环境中,对不同的垃圾收集器进行性能测试,比较吞吐量和停顿时间。
    • 监控 GC 行为:使用 GC 日志和监控工具,观察垃圾收集器的工作情况。
  4. 注意 JVM 版本

    • 升级 JVM:新的垃圾收集器通常在较新的 JVM 版本中提供,如 ZGC 在 JDK 11 及以上版本可用。
    • 兼容性测试:升级 JVM 时,确保应用在新版本上运行正常。

监控并分析 GC 日志

重要性

  • 发现性能问题:通过 GC 日志可以了解垃圾回收的频率、停顿时间和内存回收效果,及时发现问题。
  • 指导调优决策:分析 GC 日志的数据,有助于我们调整内存参数和垃圾收集器设置。

实践建议

  1. 启用 GC 日志

    • 在 JVM 启动参数中添加 -XX:+PrintGCDetails-XX:+PrintGCDateStamps-Xloggc:<filename>,记录详细的 GC 日志。
  2. 定期分析 GC 日志

    • 使用分析工具:借助 GCViewerGCEasy 等工具,对 GC 日志进行可视化分析。
    • 关注关键指标
      • GC 频率:垃圾回收是否过于频繁,可能需要调整堆或年轻代大小。
      • 停顿时间:GC 导致的应用停顿时间是否可接受,过长的停顿时间需要优化。
      • 内存回收效果:每次 GC 回收的内存量,观察是否有内存泄漏。
  3. 设置合理的日志滚动策略

    • 防止日志过大:使用日志滚动和压缩,避免 GC 日志文件占用过多磁盘空间。
    • 保留历史数据:根据需求保留一定期限的 GC 日志,方便追溯问题。
  4. 结合监控平台

    • 实时监控:使用 APM(Application Performance Management)工具,如 PrometheusGrafana,实时监控 GC 指标。
    • 告警设置:对关键指标设置告警阈值,及时通知相关人员。

避免内存泄漏

重要性

  • 保障稳定性:内存泄漏会导致内存持续增长,最终可能引发 OutOfMemoryError,导致应用崩溃。
  • 提升性能:内存泄漏会增加垃圾回收的负担,影响应用的性能。

实践建议

  1. 定期进行内存分析

    • 生成堆转储:使用 jmap 或在应用异常时生成堆转储文件。
    • 使用分析工具:借助 MATVisualVM 等工具,分析堆转储,查找内存泄漏的根源。
  2. 注意常见的内存泄漏场景

    • 静态集合持有对象引用:例如,将对象添加到静态的 ListMap 中,未及时清理。
    • 监听器和回调未解除:注册的事件监听器或回调未在适当时机解除,导致对象无法被回收。
    • 线程池和定时任务:未正确关闭线程池或定时任务,导致线程一直存活,关联的对象无法回收。
    • 缓存未清理:使用自定义缓存时,未设置过期策略或容量限制。
  3. 遵循良好的编码规范

    • 及时释放资源:对于使用完毕的资源,如数据库连接、文件流等,确保在 finally 块或使用 try-with-resources 语句及时关闭。
    • 避免过长的对象生命周期:尽量缩小对象的作用域,不要在不必要的情况下将对象提升为全局变量。
    • 使用弱引用:对于可有可无的缓存或监听器,可以使用 WeakReferenceSoftReference 等弱引用类型。
  4. 进行代码审查和测试

    • 代码审查:定期对代码进行审查,关注可能导致内存泄漏的编码方式。
    • 内存测试:编写单元测试和压力测试,模拟长时间运行,观察内存占用情况。

8. 案例分析

高并发 Web 应用的 GC 调优

问题描述

一家互联网公司开发了一款高并发的 Web 应用,提供实时数据查询和交易功能。在用户访问高峰期,服务器频繁出现响应延迟甚至超时的情况。通过监控,发现以下问题:

  • CPU 使用率偏高:CPU 长时间处于 80% 以上的占用率。
  • GC 频繁:垃圾回收次数频繁,特别是 Full GC 次数较多。
  • GC 停顿时间长:每次 Full GC 的停顿时间达数百毫秒到几秒,影响了用户体验。
  • 内存使用不稳定:老年代内存占用率持续升高,偶尔会出现内存溢出错误(OutOfMemoryError)。
调优步骤

1. 收集 GC 日志

  • 启用 GC 日志:在 JVM 启动参数中添加:

    -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/path/to/gc.log
    
  • 分析 GC 日志:使用 GCViewer 对收集到的 GC 日志进行分析,发现:

    • Minor GC 频率高:年轻代垃圾回收非常频繁,间隔时间很短。
    • Full GC 次数多:老年代空间被迅速占满,导致频繁的 Full GC。
    • GC 停顿时间长:Full GC 的平均停顿时间较长,严重影响了应用的响应时间。

2. 分析内存使用情况

  • 使用 jstat 工具

    jstat -gcutil <pid> 1000 100
    

    发现老年代(Old Generation)的使用率持续上升,未能及时回收。

  • 生成堆转储文件

    jmap -dump:format=b,file=heapdump.hprof <pid>
    

    通过 MAT 工具分析堆转储,发现大量的短生命周期对象没有被及时回收。

3. 调整 JVM 内存参数

  • 增大堆内存:将堆内存从 4GB 增加到 8GB。
    -Xms8g -Xmx8g
    
  • 调整年轻代大小:增大年轻代(Young Generation)大小,使其占堆内存的 60%。
    -Xmn4.8g
    
  • 调整 Survivor 区比例:优化对象在 Eden 和 Survivor 区的分配。
    -XX:SurvivorRatio=8
    
  • 设置新生代晋升阈值:增加对象在年轻代存活的次数,减少对象过早进入老年代。
    -XX:MaxTenuringThreshold=15
    

4. 更换垃圾收集器

  • 从 Parallel GC 切换到 G1 GC
    -XX:+UseG1GC
    
  • 设置 G1 GC 参数
    • 最大停顿时间目标
      -XX:MaxGCPauseMillis=200
      
    • 并行 GC 线程数
      -XX:ParallelGCThreads=8
      
    • 并发标记线程数
      -XX:ConcGCThreads=4
      

5. 优化代码

  • 减少对象创建:审查代码,发现某些业务逻辑频繁创建临时对象,进行优化,使用对象池或复用机制。
  • 使用缓存:对于热点数据,使用缓存减少重复的对象创建和数据库访问。
调优结果
  • GC 频率降低:Minor GC 和 Full GC 的次数显著减少。
  • 停顿时间缩短:GC 停顿时间降低到平均 100 毫秒以内。
  • CPU 使用率下降:CPU 占用率降低到 50% 左右。
  • 系统稳定性提高:在高并发场景下,应用响应时间稳定,未再出现超时和内存溢出问题。

内存泄漏的定位和处理

问题描述

某金融公司开发的交易系统在长时间运行后,出现内存溢出错误(OutOfMemoryError: Java heap space),需要定期重启服务。问题现象包括:

  • 内存使用持续增长:堆内存使用率不断攀升,直至耗尽。
  • GC 无法回收内存:频繁的 Full GC 后,老年代内存使用率仍然居高不下。
堆转储分析

1. 生成堆转储文件

  • 当出现内存溢出时,设置 JVM 参数自动生成堆转储文件:
    -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/heapdump.hprof
    
  • 或者使用 jmap 手动生成堆转储。

2. 使用 MAT 分析堆转储

  • 打开堆转储文件:在 MAT 中打开生成的 heapdump.hprof 文件。
  • 查看内存占用:使用 MAT 的“Dominator Tree”功能,找到占用内存最大的对象。
  • 查找泄漏的对象:发现某个自定义的缓存对象占用了大量内存,其内部维护的 Map 持有大量的键值对。
  • 分析对象引用:使用“Path to GC Roots”功能,发现这些对象被静态变量引用,导致无法被 GC 回收。
问题解决

1. 修复代码

  • 问题原因:应用程序在使用自定义缓存时,没有对缓存的大小和生命周期进行控制,导致缓存对象不断增长。
  • 解决方案
    • 引入缓存淘汰策略:使用 LinkedHashMap 实现 LRU(最近最少使用)策略,限制缓存的最大容量。
      private static final int MAX_CACHE_SIZE = 1000;
      private static Map<String, Object> cache = new LinkedHashMap<String, Object>(MAX_CACHE_SIZE, 0.75f, true) {
              
              
          protected boolean removeEldestEntry(Map.Entry<String, Object> eldest) {
              
              
              return size() > MAX_CACHE_SIZE;
          }
      };
      
    • 使用成熟的缓存框架:如 Guava Cache、Caffeine 等,提供更丰富的缓存特性和良好的内存管理。

2. 部署新版本

  • 测试验证:在测试环境中部署修复后的应用,运行长时间压力测试,观察内存使用情况。
  • 上线部署:将修复后的应用部署到生产环境,持续监控内存使用和 GC 情况。

3. 监控和预警

  • 设置内存使用告警:当堆内存使用率超过 80% 时,触发告警,及时介入处理。
  • 定期分析堆转储:定期生成并分析堆转储,预防新的内存泄漏。

通过以上两个案例,我们可以看到在 JVM 调优和内存问题排查过程中,以下实践是至关重要的:

  • 全面的监控和日志收集:及时获取 GC 日志、内存使用情况,为问题分析提供数据支持。
  • 深入的工具使用:熟练使用 MAT、GCViewer 等分析工具,快速定位问题根源。
  • 合理的参数调整:根据应用特点,调整 JVM 参数,选择合适的垃圾收集器和内存设置。
  • 代码层面的优化:很多性能问题和内存泄漏最终需要从代码层面解决,良好的编码实践和及时的代码审查非常重要。

9. 结论

持续调优的重要性

在当今快速变化的技术环境中,JVM 调优并非一劳永逸的任务,而是一个需要持续关注和实践的过程。应用程序的运行环境、负载和业务需求都可能随着时间而变化,这使得持续调优成为保障系统性能和稳定性的关键因素。

  • 适应业务增长和变化:随着用户数量的增加和业务规模的扩展,应用程序可能会遇到新的性能瓶颈。持续的 JVM 调优可以帮助系统及时适应这些变化,保持良好的性能。

  • 应对复杂的运行环境:云计算、容器化和微服务架构的广泛应用,使得运行环境更加复杂。持续调优可以确保 JVM 在各种环境下都能高效运行。

  • 预防和解决潜在问题:通过定期监控和分析 JVM 的运行状态,可以及早发现内存泄漏、线程死锁等问题,防止小问题演变成影响系统稳定性的重大故障。

  • 优化资源利用,降低成本:持续调优可以提高硬件资源的利用率,减少不必要的资源浪费,从而降低运营成本。

全面的知识储备对调优的影响

成功的 JVM 调优离不开对相关知识的深入理解。全面的知识储备可以帮助开发者和运维人员做出明智的调优决策,避免盲目尝试和误判。

  • 深入理解 JVM 原理:掌握 JVM 的内存结构、垃圾回收机制和参数配置等核心概念,是进行有效调优的基础。只有理解了 JVM 内部的工作原理,才能针对具体问题选择合适的调优策略。

  • 熟悉垃圾回收器和算法:不同的垃圾回收器和算法适用于不同的场景。了解它们的特点和适用范围,可以根据应用的需求选择最佳的垃圾回收方案,平衡吞吐量和停顿时间。

  • 掌握性能监控和分析工具的使用:熟练使用 jstatjmapjstack、VisualVM、MAT 等工具,可以帮助快速定位性能瓶颈和内存问题,提高调优效率。

  • 了解应用程序的特性和业务逻辑:调优不仅仅是调整 JVM 参数,还需要结合应用程序的具体情况。了解对象的生命周期、线程模型和并发特性,有助于制定更有效的调优方案。

  • 持续学习和实践:技术发展日新月异,新版本的 JVM、新的垃圾回收器和调优工具不断涌现。保持对最新技术的学习和实践,可以帮助我们在调优过程中取得更好的效果。

10. 参考资料

在深入学习和实践 JVM 调优的过程中,以下参考资料将为你提供宝贵的知识和指导:

  1. 《深入理解 Java 虚拟机:JVM 高级特性与最佳实践》,作者:周志明

    • 简介:这本书被誉为 JVM 领域的经典之作,详细讲解了 JVM 的体系结构、内存模型、垃圾回收机制以及性能调优的最佳实践。作者以深入浅出的方式,结合大量实例,帮助读者全面理解 JVM 的内部原理。
    • 适用读者:中高级 Java 开发者,对 JVM 内部机制和性能优化感兴趣的技术人员。
  2. Oracle 官方文档:Java SE Documentation

    • 链接Java SE Documentation
    • 内容概述:Oracle 提供的官方文档,包括 Java SE 平台的技术规范、JVM 参数说明、垃圾回收器指南、工具使用手册等。对于深入了解 JVM 参数配置、垃圾回收器特性和调优工具的使用非常有帮助。
    • 推荐理由:权威性强,内容全面,适合查阅具体技术细节和官方建议。
  3. 技术博客和社区

    • InfoQ 的 JVM 调优文章

      • 链接InfoQ Java 专区
      • 内容概述:InfoQ 汇集了大量 JVM 调优相关的高质量技术文章、案例分析和专家访谈。涵盖了最新的 JVM 技术趋势、垃圾回收器深入解析和性能调优实战经验。
      • 推荐理由:内容新颖,实用性强,适合了解业界最佳实践和前沿技术。
    • CSDN 上的相关讨论

      • 链接CSDN JVM 专栏
      • 内容概述:CSDN 是国内最大的开发者社区之一,包含大量 JVM 调优的技术博文、经验分享和问答讨论。社区活跃,资源丰富。
      • 推荐理由:适合获取实际项目中的调优经验,参与技术讨论,解决实际问题。
  4. 官方垃圾回收调优指南

    • 链接Java Garbage Collection Tuning Guide
    • 内容概述:Oracle 提供的垃圾回收器调优指南,详细介绍了各种垃圾回收器的工作原理、参数配置和调优方法。
    • 推荐理由:官方权威指南,适合深入理解垃圾回收机制和进行参数调优。
  5. 《Java 性能调优指南》,作者:Scott Oaks

    • 简介:这本书深入探讨了 Java 应用程序的性能调优,包括 JVM 性能监控、垃圾回收调优、线程和并发优化等主题。
    • 适用读者:对 Java 性能优化有系统性需求的开发者和架构师。
  6. Java 性能调优相关的网站和博客

    • Mechanical Sympathy

      • 链接Mechanical Sympathy 博客
      • 作者:Martin Thompson
      • 内容概述:聚焦于高性能系统的构建,涵盖了 JVM 优化、并发编程和硬件亲和性等主题。
    • RedHat Developer Blog

  7. 社区论坛和问答平台

    • Stack Overflow

      • 链接Stack Overflow JVM 调优问题
      • 内容概述:全球最大的开发者问答社区,有大量关于 JVM 调优的实践问题和专家解答。
      • 推荐理由:适合解决具体的技术难题,获取实践经验。
    • GitHub

      • 链接GitHub 上的 JVM 调优项目
      • 内容概述:开源社区的项目和工具,包含 JVM 调优脚本、配置模板和性能测试工具。
      • 推荐理由:直接获取可用的工具和代码,方便实践。

猜你喜欢

转载自blog.csdn.net/weixin_43114209/article/details/143154148