详细剖析JVM以及如何调优

本文详细介绍了JVM的内存模型、类加载过程、垃圾回收机制等内容。涵盖了从对象的创建到销毁的全过程,深入剖析了垃圾回收算法及其实现原理。

一、前言

JVM调优并非参数堆砌的艺术,而是一场基于数据与监控的精准手术。它旨在平衡内存、吞吐量与延迟,让Java应用在资源约束下发挥最大效能。

核心在于理解应用特性:是追求低延迟的金融交易,还是高吞吐的批处理?不同的目标,决定了完全不同的优化路径。

二、调优前的核心准备

在动手调整任何参数前,充分的准备是成功的一半。盲目调优往往适得其反。

2.1 明确性能目标

调优必须有明确目标导向,通常聚焦于三者之一: 低延迟:最小化垃圾回收(GC)导致的停顿时间(Stop-the-World),适用于实时交互系统。 高吞吐量:最大化应用在单位时间内的处理能力,适合离线计算、数据分析任务。 小内存占用:在有限内存资源下稳定运行,常见于容器化或资源受限环境。

2.2 收集与分析数据

没有度量,就没有优化。必须建立监控基线: 启用详细GC日志:使用 -Xlog:gc* 或 -XX:+PrintGCDetails 参数记录每次GC行为。 利用监控工具jstat -gc <pid> 1s 实时观察;jmap 分析堆内存分布;VisualVM、JProfiler进行深度剖析。 压力测试:在模拟真实负载下观察JVM表现,记录瓶颈。

三、JVM的内存模型

四、JAVA类加载的全过程是怎样的?什么是双亲委派机制?有什么作用?

  • JAVA的类加载器
AppClassloader -> ExtClassloader -> BootStrap->Classloader
每种类加载器都有他自己的加载目录。
  • JAVA中的类加载器:
AppClassLoader ->ExtClassLoader -> URLClassLoader - >SecureClassLoader -> ClassLoader
每个类加载器对他加载过的类,都是有一个缓存的。
  • 双亲委派
向上委托查找,向下委托加载。 作用:保护JAVA的层的类不会被应用程序覆盖。
  • 类加载过程
       1. 加载:把Java的字节码数据加载到JVM内存当中,并映射成JVM认可的数据结构。
       2. 连接:分为三个小的阶段:
                  2.1 验证:检查加载到的字节信息是否符合JVM规范。
                  2.2 准备: 创建类或接口的静态变量,并赋初始值 半初始化状态
                  2.3 解析:把符号引用转为直接引用
       3. 初始化:
  • 一个对象从加载到JVM,再到被GC清除,都经历了什么过程?
method{ ClassLoaderDemo1 c =new ClassLoaderDemo1(); c.xxx} GC
1. 用户创建一个对象,JVM首先需要到方法区去找对象的类型信息。然后再创建对
象。
2. JVM要实例化一个对象,首先要在堆当中先创建一个对象。-> 半初始化状态
3. 对象首先会分配在堆内存中新生代的Eden。然后经过一次Minor GC,对象如果存活,就会进入S区。在后续的每次GC中,如果对象一直存活,就会在S区来回拷贝,每移动一次,年龄加1。-> 多大年龄才会移入老年代? 年龄最大15, 超过一定年龄后,对象转入老年代。
4. 当方法执行结束后,栈中的指针会先移除掉。
5. 堆中的对象,经过Full GC,就会被标记为垃圾,然后被GC线程清理掉。

五、怎么确定一个对象到底是不是垃圾? 什么是GC Root?

有两种定位垃圾的方式:
1、引用计数: 这种方式是给堆内存当中的每个对象记录一个引用个数。引用个数
为0的就认为是垃圾。这是早期JDK中使用的方式。引用计数无法解决循环引用的问
题。
2、根可达算法: 这种方式是在内存中,从引用根对象向下一直找引用,找不到的
对象就是垃圾。
哪些是GC Root? Stack -> JVM Stack, Native Stack, class类, run-time
constant pool 常量池, static reference 静态变量。

六、JVM有哪些垃圾回收算法?

  • MarkSweep 标记清除算法
这个算法分为两个阶段,标记阶段:把垃圾内存标记出来,清除阶段:直接将垃圾内存回收。
这种算法是比较简单的,但是有个很严重的问题,就是会产生大量的内存碎片。
  • Copying 拷贝算法
为了解决标记清除算法的内存碎片问题,就产生了拷贝算法。拷贝算法将内存分为大小相等的两半,每次只使用其中一半。垃圾回收时,将当前这一块的存活对象全部拷贝到另一半,然后当前这一半内存就可以直接清除。
这种算法没有内存碎片,但是他的问题就在于浪费空间。而且,他的效率跟存货对象的个数有关。
  • MarkCompack 标记压缩算法
为了解决拷贝算法的缺陷,就提出了标记压缩算法。这种算法在标记阶段跟标记清除算法是一样的,但是在完成标记之后,不是直接清理垃圾内存,而是将存活对象往一端移动,然后将端边界以外的所有内存直接清除。

七、JVM有哪些垃圾回收器?他们都是怎么工作的?什么是STW?他都发生在哪些阶段?什么是三色标记?如何解决错标记和漏标记的问题?为什么要设计这么多的垃圾回收器?

7.1 STW

Stop-The-World。是在垃圾回收算法执行过程当中,需要将JVM内存冻结的一种状态。

在STW状态下,JAVA的所有线程都是停止执行的-GC线程除外,native 方法可以执行,但是,不能与JVM交互。GC各种算法优化的重点,就是减少STW,同时这也是JVM调优的重点。

7.2 JVM的垃圾回收器

Serial 串行。整体过程比较简单,就像踢足球一样,需要GC时,直接暂停,GC完了再继续。 这个垃圾回收器,是早期垃圾回收器,只有一个线程执行GC。在多CPU架构下,性能就会下降严重。只适用于几十兆的内存空间。
Parallel 并行在串行基础上,增加多线程GC。PS+PO这种组合是JDK1.8默认的垃圾回收器。在
多CPU的架构下,性能会比Serial高很多。
CMS Concurrent Mark Sweep 核心思想,就是将STW打散,让一部分GC线程与用户线程并发执行。

7.3 整个GC过程分为四个阶段

1、初始标记阶段:STW 只标记出根对象直接引用的对象。
2、并发标记:继续标记其他对象,与应用程序是并发执行。
3、重新标记: STW 对并发执行阶段的对象进行重新标记。
4、并发清除:并行。将产生的垃圾清除。清除过程中,应用程序又会不断的产生新的垃圾,叫做浮动垃圾。这些垃圾就要留到下一次GC过程中清除。
G1 Garbage First 垃圾优先他的内存模型是实际不分代,但是逻辑上是分代的。在内存模型中,对于堆内存就不再分老年代和新生代,而是划分成一个一个的小内存块,叫做Region。每个Region可以隶属于不同的年代。

7.4 GC分为五个阶段

第一:初始标记 标记出GCRoot直接引用的对象。STW
第二:标记Region,通过RSet标记出上一个阶段标记的Region引用到的Old区Region。
第三:并发标记阶段:跟CMS的步骤是差不多的。只是遍历的范围不再是整个Old区,而只需要遍历第二步标记出来的Region。
第四:重新标记: 跟CMS中的重新标记过程是差不多的。
第五:垃圾清理:与CMS不同的是,G1可以采用拷贝算法,直接将整个Region中的对象拷贝到另一个Region。而这个阶段,G1只选择垃圾较多的Region来清理,并不是完全清理。

7.5 CMS的核心算法就是三色标记

三色标记:是一种逻辑上的抽象。
将每个内存对象分成三种颜色:
黑色:表示自己和成员变量都已经标记完毕。
灰色:自己标记完了,但是成员变量还没有完全标记完。
白色:自己未标记完。
CMS通过增量标记 increment update 的方式来解决漏标的问题。

八、内存区域与核心参数调优

JVM内存是调优的主战场,合理分配是稳定性的基石。堆内存主要分为新生代(Young Generation)和老年代(Old Generation),元空间(Metaspace)则存储类元数据。

堆内存配置黄金法则

堆大小设置是调优第一步,遵循以下原则可避免常见陷阱:

1.固定堆大小,避免动态扩容

将初始堆(-Xms)与最大堆(-Xmx)设为相同值,例如 -Xms4g -Xmx4g。这能防止JVM运行时因扩容/收缩堆内存而产生的性能抖动。

2. 合理分配新生代与老年代

新生代大小(-Xmn)通常设置为堆的1/4到1/3。过小会导致频繁Minor GC,过大会延长Full GC时间。例如8G堆可设 -Xmn2g

3. 关注元空间限制

对于动态生成类较多的应用(如使用Spring、Groovy),需设置元空间上限 -XX:MaxMetaspaceSize=512m,防止元空间无限膨胀导致内存溢出。

九、垃圾回收器选择与配置

垃圾回收器(GC)的选择是影响性能最关键的决定之一。JVM提供了多种GC实现,各有其适用场景。

回收器核心目标启用参数适用场景
G1 (Garbage-First)平衡吞吐与延迟,可预测停顿-XX:+UseG1GCJDK9+默认,大内存、响应时间敏感型应用
ZGC超低延迟(暂停<10ms)-XX:+UseZGCJDK11+,对延迟有极致要求的金融、交易系统
Parallel GC高吞吐量-XX:+UseParallelGCJDK8默认,后台计算、批处理任务
Serial GC最小内存占用-XX:+UseSerialGC单核CPU或微服务、客户端应用

以最常用的G1回收器为例,关键调优参数包括:

  • -XX:MaxGCPauseMillis=200:设置期望的最大GC停顿时间目标(毫秒),G1会尽力达成但不保证。
  • -XX:InitiatingHeapOccupancyPercent=45:触发并发标记周期的堆占用率阈值,默认45%。
  • -XX:G1NewSizePercent / -XX:G1MaxNewSizePercent:控制新生代占比范围(默认5%-60%)。

十、高级调优与场景化实践

掌握了基础配置后,需要针对特定场景和高级需求进行精细化调整。

🎯 场景化配置方案

  • 低延迟场景(如金融交易)

核心是选用ZGC或Shenandoah,并限制堆内存软上限以防止内存占用过高影响响应。

java -XX:+UseZGC -Xms24g -Xmx24g -XX:ConcGCThreads=6 \
     -XX:SoftMaxHeapSize=20g -jar app.jar
  • 容器化环境(如Kubernetes)

必须启用容器支持,并基于内存限制按比例分配,避免容器因内存超限被杀死。

java -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 \
     -XX:InitialRAMPercentage=50.0 -XX:+UseG1GC -jar app.jar
  • 高吞吐量场景(如数据处理)

选用Parallel GC,并调整GC线程数以匹配CPU核心数,最大化利用计算资源。

java -XX:+UseParallelGC -XX:ParallelGCThreads=8 \
     -Xms8g -Xmx8g -jar batch-app.jar

其他关键优化参数

  • -XX:+AlwaysPreTouch:启动时预分配并触摸所有内存页,避免运行时因缺页中断产生延迟,但会延长启动时间。
  • -XX:+UseStringDeduplication (G1):启用字符串去重,节省堆内存,尤其适用于大量重复字符串的应用。
  • -XX:+ExplicitGCInvokesConcurrent:让System.gc()触发并发GC,避免产生完全STW的Full GC。
  • -XX:+HeapDumpOnOutOfMemoryError:在内存溢出时自动生成堆转储文件,是定位内存泄漏的利器。

十一、常见问题与解决思路

调优过程中,总会遇到一些典型问题。以下是快速诊断与解决的思路。

问题现象可能原因排查与解决方向
Full GC频繁老年代空间不足、内存泄漏、对象过早晋升检查对象生命周期;增大堆或老年代;调整-XX:MaxTenuringThreshold(晋升年龄);分析堆转储。
GC停顿时间过长大对象分配、Finalizer队列阻塞、高晋升率避免创建大数组或大字符串;用Cleaner替代finalize();优化Survivor区比例。
内存占用居高不下堆设置过大、缓存未释放、元空间膨胀限制-XX:MaxMetaspaceSize;使用软引用缓存(如Caffeine);检查第三方库的内存占用。
应用吞吐量不达标GC线程占用过多CPU、锁竞争激烈、JIT编译开销大调整-XX:ParallelGCThreads;优化同步代码块;考虑使用-XX:+TieredCompilation分层编译。

十二、调优心法:原则与误区

最后,记住这些原则能让你在调优路上少走弯路。

必须遵循的调优原则

先测量,后调优:永远基于监控数据(GC日志、性能指标)做决策,而非猜测。 一次只改一个变量:每次只调整一个参数并观察效果,否则无法定位变化原因。 回归测试:任何参数调整后,必须进行功能与性能回归测试,确保稳定性。 代码优化优先:JVM调优是“最后一公里”的优化。优先考虑减少对象创建、复用缓冲区、选择高效算法等代码级优化。

需要警惕的常见误区

盲目追求“最优”参数:不存在放之四海而皆准的最优配置,必须贴合应用实际负载。 参数堆砌:添加大量未经验证的“优化”参数,可能引入副作用或兼容性问题。 忽视操作系统与硬件:NUMA架构、SSD磁盘、网络带宽等底层资源同样影响JVM表现。 将性能问题简单归咎于JVM:如资深开发者所言,多数性能问题源于应用程序本身,而非JVM。完善的日志和监控是定位问题的前提。

十三、总结

调优的本质是理解与平衡

JVM调优不是魔法,而是一门基于监控数据、应用特性和资源约束的工程实践。它是在内存、CPU时间和响应延迟之间寻找那个微妙的平衡点。记住,没有“最好”的配置,只有“最适合”当前场景的配置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thomas.Sir

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值