JVM的底层本质是一台运行在操作系统之上的抽象计算机,它负责把字节码翻译成机器指令并管理内存;性能优化的关键,不是堆内存越大越好,而是让垃圾回收节奏与业务特征匹配。
JVM 像一位带着行李自己旅行的大管家,Java 代码经过编译变成 .class 字节码,自身无法被操作系统直接执行,JVM 就把字节码逐条解释、编译成当前系统的机器码,同时负责分配内存、回收垃圾、协调线程,理解了它在干什么,你就理解了 JVM 调优为什么那么多人头疼,因为它实际上是在管理两个东西:内存和线程。
JVM内存模型详解与运行时数据区是怎么分工的
JVM 运行时把内存划分成几个区域,各管各的活,这套划分规则在 Oracle 官方文档里有明确定义,业内统称为“内存模型”,面试和工作中聊的“JVM内存模型详解”,99% 是在讲下面这些区域的分工。
堆区:对象的大本营
堆区是所有对象实例和数组的居住地,这块内存是所有线程共享的,也是垃圾回收的主战场,堆内部会进一步划分为年轻代和老年代,年轻代又分为 Eden 区和两块幸存者区。
绝大多数新建对象先进入 Eden 区,经过一轮垃圾回收后仍然存活的对象,会被移动到幸存者区,幸存者区的对象经过多次回收仍然存活,最后晋升到老年代。这个晋升过程依赖年龄计数器,每熬过一次回收年龄加一,超过阈值就能晋级。
关于堆的默认大小,行业共识是逻辑内存的四分之一作为上限,但这个数值并不适合所有业务,后面性能优化部分我会重点展开。
栈区:方法执行的临时舞台
栈区是线程私有的,每个线程启动时,JVM 都会为他分配一块栈空间,每当执行一个方法,JVM 就压入一个栈帧,栈帧里存放局部变量、操作数栈、动态链接和方法出口。
栈的生命周期跟方法绑定,方法执行完毕,栈帧自动弹出,无需垃圾回收器介入,所以局部变量用完就销毁,效率比堆高得多,不过如果方法递归调用深度过大,就会出现 StackOverflowError,这是栈空间耗尽的信号。
元空间与直接内存
JDK 8 以前,类元数据存储在永久代;JDK 8 之后,永久代被元空间取代,元空间不在 JVM 堆内,而是使用了操作系统直接内存,类信息、常量池、方法描述符都放在这里。
直接内存也叫堆外内存,NIO 类库可以通过 DirectByteBuffer 直接使用它,Java 的 netty、kafka 客户端大多采用堆外内存做数据缓冲,减少一次拷贝,这个区域如果使用不当也会撑爆,排查的时候别只盯着堆。
JVM垃圾回收器怎么选:从 Serial 到 ZGC 的对比
JDK 究竟自带多少种垃圾回收器?答案是一套演进谱系,垃圾回收器选择,在相当大程度上决定了你的停顿时间。
分代回收器的阵营
JVM 早期使用分代收集器,包括 Serial 单线程回收器、Parallel 并行回收器和 CMS 并发标记清除回收器。
- Serial:单线程回收,适合单核环境或小应用。
- Parallel:多线程并行回收,吞吐量优先,适合批量计算和后台任务。
- CMS:以最短停顿为目标,但会产生碎片,JDK 14 已被官方移除。
JDK 9 之后,G1 成为默认回收器,G1 把堆划分成多个相等区域,每次回收一部分区域的垃圾,而不是全堆扫描,它设计目标是在停顿可控的同时兼顾吞吐量,多数 Java 服务跑在 G1 上。
JDK 15 之后,ZGC 正式转正,ZGC 支持 TB 级堆,停顿时间几乎不随堆变大而增加,但代价是更耗 CPU。
不同回收器使用场景对比
| 回收器 | 核心思想 | 适合业务 | 代价 |
|---|---|---|---|
| Serial | 单线程串行 | 桌面端、极小堆 | 停顿较长 |
| Parallel | 多线程并行 | 离线计算、日志分析 | 停顿较长 |
| CMS | 并发标记清除 | 旧系统兼容遗留场景 | 碎片化,已移除 |
| G1 | 区域化分代 | 大多数线上 Java 服务 | 大堆下停顿随堆增长 |
| ZGC | 并发引用染色 | 大堆、超低延迟场景 | CPU 开销较高 |
选型建议
行业共识是:默认 G1 是一般服务的最优解,别急着换,如果业务要求极端低延迟且堆内存较大,再考虑 ZGC,小内存应用用 Serial 反而更省资源。
JVM性能优化关键点:从参数到大盘观测
性能优化不是调一个参数就完事,而是先观测,再定位,最后才调整参数,下面这套流程覆盖 Java 线上 JVM 优化的高频操作路径。
调整堆大小:先看实际占用
很多人喜欢把堆直接设置成很大的数值,效果往往适得其反,堆太大,垃圾回收单次时间长;堆太小,频繁触发 Full GC,CPU 白忙活。
合理做法是先在负载测试环境开启 GC 日志:
java -Xlog:gc:gc.log
跑几天业务后,观察 GC 日志里堆实际使用峰值,然后设置:
-Xms4g -Xmx4g
将初始堆和最大堆设为一致,避免运行期动态扩容带来的性能抖动。这个是 JVM 调优最佳实践里最基础的一步。
Java线上OOM怎么排查:三步定位法
线上发生 OutOfMemoryError,很多团队第一反应是重启大法,但重启之后还会复现,不如按这套流程走:
- 启动时加上堆转储参数,让 JVM 在 OOM 时自动保存现场:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dump
- 使用 jmap 手动导出现存堆快照:
jmap -dump:format=b,file=heap.hprof <pid>
- 用 MAT 或 VisualVM 分析直方图,查看哪个对象实例数量最多、哪个类占用的 Retained Heap 最大。
定位到对象后,多数情况是因为集合类缓存无限增长,或 SQL 取数没有分页,修复代码,比调大堆更有效。
CPU 飙高的排查命令
CPU 使用率居高不下,常见原因有两个:频繁 Full GC 和业务线程死循环。
- 先用
top -Hp <pid>找出CPU最高的线程 ID。 - 将线程 ID 转成十六进制:
printf '%xn' <tid>。 - 执行
jstack <pid> > thread.log,在线程快照里搜索十六进制数字对应的线程栈。
这个时候你能看到它到底卡在哪段代码里,如果是 GC 线程占据大量 CPU,那要回去看 GC 日志;如果落在业务方法上,那就是代码逻辑问题。
参数优化四板斧
JVM 参数成百上千,但调优只围绕几个核心点:
- 堆大小:
-Xms和-Xmx保持一致。 - 元空间上限:设置
-XX:MaxMetaspaceSize,防止类加载器泄漏导致换机。 - GC 日志开关:
-Xlog:gc记录详情。 - OOM 自动转储:
-XX:+HeapDumpOnOutOfMemoryError。
对于 G1 回收器,还可以设置 -XX:MaxGCPauseMillis 给出停顿目标,但别设置过小,否则 G1 会为了迎合目标而频繁回收,反而拉低吞吐量。
三个容易踩的坑
- 盲目复制网上“无敌参数模板”,不经过压测直接上生产,容易出大事。
- 频繁调优堆大小而不是修复代码问题,堆只是容器,代码里的对象持有才是根因。
- 忽略堆外内存,DirectByteBuffer 使用过多会直接抛 OOM,但堆转储文件里却看不到多少对象。
JVM性能优化常见参数有哪些:一句话讲清关键参数
JVM 面试里,JVM性能优化常见参数有哪些”这个问题其实问的是基本功,把下面这张参数表记牢,基本就能应付绝大多数场景:
-Xms:初始堆大小。-Xmx:最大堆大小。-Xmn:年轻代大小,过大则老年代变小。-XX:MaxMetaspaceSize:类元数据上限。-XX:+UseG1GC:显式启用 G1。-XX:MaxGCPauseMillis:最大 GC 停顿目标。-XX:ParallelGCThreads:并行 GC 线程数。-XX:ConcGCThreads:并发标记线程数。
设置参数时,务必跟踪到底层原理。-Xmn 设置过大,年轻代对象晋升到老年代的机会变少,短期内频繁 Young GC 反而降低整体性能,任何参数调整后都要用 jstat 观察:
jstat -gcutil <pid> 1000 10
这个命令每秒输出一次各代内存使用率和 GC 耗时,连续输出 10 次,基本能判断出参数调整是否起了效果。
关于JVM知识的高频疑问解答
从堆内存角度,JVM内存模型和Java内存模型有区别吗?
有本质区别,JVM内存模型描述的是运行时数据区的划分,也就是前面提到的堆、栈、方法区等具体内存区域,而 Java 内存模型是围绕着多线程并发场景定义的一套抽象规则,解决的是可见性、有序性和原子性问题,对应 synchronized、volatile 这些关键字背后的语义,前者讲数据放哪里,后者讲多线程读写数据时怎么保证正确性。
线上 JVM 堆内存一直偏高但没到最大值,需要人为干预吗?
多数情况下不需要立即干预,先执行 jstat -gcutil <pid> 观察老年代使用率和 Full GC 频率,如果老年代占比持续上升且一直没有回落到安全水位,说明存在对象长期无法回收的可能,这时候再配合 jmap -dump 检查是否有对象意外持有大集合,优先处理代码层面的持有关系,而不是增加堆大小。
JDK 21 里 G1 和 ZGC 怎么选?
JDK 21 中 G1 仍是默认垃圾回收器,ZGC 从实验状态转正之后已经经过多个版本迭代,在低延迟方向上表现稳定,如果你的服务堆内存较小且追求最大吞吐量,继续用 G1;如果堆内存超过几十 GB,并且业务要求极端响应速度,ZGC 带来的停顿优势优于吞吐量的轻微下降,选择核心在于衡量你的业务到底缺停顿时间,还是缺 CPU 余量。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/615173.html





