JVM 调优入门与面试
目录一、JVM 调优是什么二、JVM 内存结构调优的前提2.1 堆Heap— 调优的主战场2.2 栈Stack— 线程私有2.3 方法区元空间三、GC 垃圾回收机制3.1 怎么判断对象是垃圾3.2 垃圾回收算法3.3 常见的垃圾回收器四、JVM 参数分类4.1 堆内存参数4.2 GC 相关参数4.3 栈和线程参数4.4 OOM 相关参数五、JVM 调优实战流程第一步拿到监控数据没数据别调第二步分析 GC 日志关键指标第三步根据问题调参场景 1Young GC 太频繁场景 2Full GC 频繁场景 3GC 停顿时间太长场景 4元空间 OOM场景 5栈溢出场景 6OOM 了但不知道哪里六、典型应用场景的 JVM 参数模板6.1 微服务2C4G6.2 大内存服务8C16G6.3 低延迟场景ZGCJDK 17七、面试话术汇总Q1JVM 调优从哪开始Q2Full GC 频繁怎么办Q3怎么判断用 Parallel 还是 G1Q4-Xms 和 -Xmx 为什么要设一样Q5OOM 怎么排查Q6元空间和永久代有什么区别Q7STW 是什么怎么减少Q8jstack 和 jmap 分别用在什么场景Q9对象什么时候直接进老年代Q10JVM 调优有哪些常用参数组合八、一句话速记一、JVM 调优是什么JVM 调优就是通过调整 JVM 参数让 Java 应用运行得更快、更稳、不 OOM。不是炫技而是解决实际问题问题调什么接口响应慢频繁 GC垃圾回收器选型 GC 参数系统突然 OOM 挂了堆内存大小 内存泄漏排查CPU 飙高GC 线程 / 死循环代码吞吐量上不去堆比例、并发线程数JVM 调优 先有监控数据 → 分析瓶颈 → 改参数 → 验证效果。没有监控就调优是耍流氓。二、JVM 内存结构调优的前提你调的都是下面这块地的尺寸┌──────────────────────────┐ │ 堆Heap │ │ ┌──────┬──────┬────────┐ │ │ │ 新生代│ 老年代 │ 元空间 │ │ │ │Eden S0 S1│ │ (本地) │ │ └──────┴──────┴────────┘ │ └──────────────────────────┘ 另外栈线程私有、程序计数器、本地方法栈2.1 堆Heap— 调优的主战场存放对象实例所有线程共享。分三块区域比例默认特点新生代Young堆的 1/3新对象在这GC 频繁└ Eden新生代的 8/10对象首先分配在这└ Survivor From新生代的 1/10GC 后存活的对象移到这└ Survivor To新生代的 1/10复制算法用始终是空的老年代Old堆的 2/3长期存活的对象在这GC 少元空间MetaSpace默认无上限类信息、常量池不归堆管2.2 栈Stack— 线程私有每个线程一个栈存局部变量、方法调用链。栈深度不够就StackOverflowError。2.3 方法区元空间存类信息、常量、静态变量。Java 8 之前叫永久代PermGen之后改元空间MetaSpace从堆移到了本地内存。为什么改永久代大小固定类多了容易 OOMPermGen space。元空间用本地内存理论只受物理内存限制。三、GC 垃圾回收机制3.1 怎么判断对象是垃圾可达性分析算法面试必问GC Roots → 对象A → 对象B → 对象C → 对象D可达存活 → 对象E不可达回收GC Roots 包括栈帧中的局部变量、静态变量、JNI 引用。3.2 垃圾回收算法算法原理适用标记-清除标记垃圾 → 清除老年代CMS 用标记-复制分两块用一块存活的复制到另一块新生代默认标记-整理标记存活 → 向一端移动 → 清理边界外老年代3.3 常见的垃圾回收器回收器适用代特点常用参数Serial新生代单线程STW 长客户端默认ParNew新生代Serial 的多线程版CMS 的搭档Parallel Scavenge新生代关注吞吐量服务端默认JDK 8Parallel Old老年代吞吐量优先跟 Parallel Scavenge 配对CMS老年代低延迟并发JDK 9 起废弃G1全堆分区回收可预测停顿JDK 9 默认ZGC全堆极低延迟10msJDK 11 实验性JDK 17 可用Shenandoah全堆同 ZGCRed Hat 出品JDK 12一句话选型JDK 8 默认Parallel Scavenge Parallel Old吞吐量优先 JDK 9 默认G1平衡延迟和吞吐量 追求低延迟10msZGC大堆大内存 小应用4G 堆Parallel 够用四、JVM 参数分类4.1 堆内存参数# 堆大小 -Xms4g # 初始堆大小启动时就分配 -Xmx4g # 最大堆大小通常 Xms避免动态扩缩 -Xmn2g # 新生代大小 # 各代比例 -XX:NewRatio2 # 老年代:新生代 2:1即新生代占堆 1/3 -XX:SurvivorRatio8 # Eden:Survivor 8:1:1 # 元空间 -XX:MetaspaceSize256m # 元空间初始大小 -XX:MaxMetaspaceSize256m # 元空间最大不设就是物理内存上限 # 堆溢出时 Dump -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/path/to/dump.hprof4.2 GC 相关参数# 选垃圾回收器 -XX:UseG1GC # 用 G1 -XX:UseParallelGC # 用 Parallel -XX:UseConcMarkSweepGC # 用 CMSJDK 9 废弃 -XX:UseZGC # 用 ZGCJDK 17 # GC 日志JDK 8 -XX:PrintGCDetails -XX:PrintGCDateStamps -Xloggc:/path/to/gc.log # GC 日志JDK 9统一格式 -Xlog:gc*:file/path/to/gc.log:time,uptime,level,tags # G1 专用 -XX:MaxGCPauseMillis200 # 期望最大停顿时间ms -XX:G1HeapRegionSize2m # G1 分区大小 -XX:InitiatingHeapOccupancyPercent45 # 触发并发 GC 的堆占比4.3 栈和线程参数-Xss256k # 每个线程的栈大小默认 1M减到 256k-512k 能开更多线程 -XX:ThreadStackSize256 # 同上单位 k4.4 OOM 相关参数-XX:HeapDumpOnOutOfMemoryError # OOM 时自动 Dump -XX:HeapDumpPath/data/dump/ # Dump 文件保存路径 -XX:OnOutOfMemoryErrorkill -9 %p # OOM 时执行脚本比如重启五、JVM 调优实战流程第一步拿到监控数据没数据别调# 1. 查看 JVM 进程 jps -l # 2. 查看堆使用情况 jstat -gc pid 1000 10 # 每 1 秒打印一次共 10 次 # 3. 查看 GC 情况 jstat -gcutil pid 1000 # 4. 线程堆栈分析 jstack pid # 5. 堆 Dump 分析 jmap -dump:live,formatb,fileheap.hprof pid # 6. 图形化分析工具 # jvisualvmJDK 8 自带 # JProfiler / MAT第三方第二步分析 GC 日志关键指标拿到 GC 日志后看什么# 一段 GC 日志Parallel 示例 2026-06-24T10:00:00.1230800: [GC (Allocation Failure) [PSYoungGen: 2048K-512K(2560K)] 2048K-1024K(7680K), 0.0012345 secs] [Times: user0.00 sys0.00, real0.00 secs]指标正常警报YGC 频率几秒到几十秒一次一秒多次YGC 耗时 50ms 200msFull GC 频率几小时甚至没有几分钟一次Full GC 耗时 200ms 1s堆使用率GC 后 20-40%GC 后还在 80%吞吐量用户时间/总时间 99% 95%第三步根据问题调参场景 1Young GC 太频繁# 现象GC 日志显示 YGC 一秒多次 # 原因新生代太小对象很快就满了 # 调大新生代 -Xmn2g # 从原来的 1G 调到 2G -XX:SurvivorRatio8 # 保持 Eden:S0:S1 8:1:1场景 2Full GC 频繁# 现象频繁 Full GC每次耗时几百毫秒甚至秒级 # 原因老年代满了多为大对象直接进老年代或内存泄漏 # 首先排查内存泄漏 jmap -dump:live,formatb,fileheap.hprof pid # 用 MAT 分析大对象 # 如果确认不是泄漏 -XX:PretenureSizeThreshold10m # 大于 10M 的对象才直接进老年代 -Xms4g -Xmx4g # 加大堆 -XX:UseG1GC # 切 G1 减少 Full GC场景 3GC 停顿时间太长# 现象用户反映请求超时GC 停顿超过 1 秒 # 方案 A切 G1 控停顿 -XX:UseG1GC -XX:MaxGCPauseMillis200 # 目标停顿 200ms # 方案 B大内存上 ZGCJDK 17 -XX:UseZGC -Xms16g -Xmx16g场景 4元空间 OOM# 现象java.lang.OutOfMemoryError: Metaspace # 主要是 CGLib 动态代理/反射生成太多类 -XX:MaxMetaspaceSize256m # 限制元空间快速暴露问题 # 然后排查哪个框架生成了大量类场景 5栈溢出# 现象StackOverflowError # 原因递归太深 # 应急方案治标不治本 -Xss512k # 加大栈但治不了无限递归 # 根本方案修代码把递归改成循环场景 6OOM 了但不知道哪里如果 OOM 了却不知道哪块内存满了加 Dump 参数# 等下次 OOM 自动 dump -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/dump/ # 然后用 MAT 分析 hprof 文件 # 重点看大对象、GC Roots 路径、线程栈六、典型应用场景的 JVM 参数模板6.1 微服务2C4Gjava -Xms2g -Xmx2g \ -Xmn1g \ -XX:UseG1GC \ -XX:MaxGCPauseMillis200 \ -XX:MetaspaceSize128m \ -XX:MaxMetaspaceSize128m \ -XX:HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath/data/dump/ \ -Xlog:gc*:file/data/logs/gc.log:time,uptime:filecount5,filesize10m \ -jar app.jar6.2 大内存服务8C16Gjava -Xms12g -Xmx12g \ -Xmn6g \ -XX:UseG1GC \ -XX:MaxGCPauseMillis100 \ -XX:ParallelGCThreads8 \ -XX:ConcGCThreads4 \ -XX:MetaspaceSize256m \ -XX:MaxMetaspaceSize256m \ -XX:HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath/data/dump/ \ -Xlog:gc*:file/data/logs/gc.log:time,uptime:filecount5,filesize20m \ -jar app.jar6.3 低延迟场景ZGCJDK 17java -Xms8g -Xmx8g \ -XX:UseZGC \ -XX:ConcGCThreads4 \ -XX:ParallelGCThreads8 \ -Xlog:gc*:file/data/logs/gc.log:time,uptime \ -jar app.jar七、面试话术汇总Q1JVM 调优从哪开始话术先做监控再调优。第一步是加 GC 日志参数用 jstat 看 GC 频率和耗时jmap 看堆使用情况。然后分析关键指标YGC 频率、Full GC 频率、单次停顿时间、堆使用率曲线。不先看监控就拍参数相当于蒙眼开车。Q2Full GC 频繁怎么办话术分两步排查。第一步排除内存泄漏——用 jmap dump 堆用 MAT 分析大对象、GC Roots 路径。第二步如果不是泄漏可以① 调大堆内存② 调大新生代比例让更多对象在 YGC 回收③ 切 G1 减少 Full GC。Full GC 频繁往往是老年代满了要么对象太多要么大对象直接进老年代了。Q3怎么判断用 Parallel 还是 G1话术堆 4G 且对停顿不敏感 → Parallel吞吐量最高。堆 4G 或需要控制停顿时间 → G1它能设置 MaxGCPauseMillis 控制每次 GC 的停顿上限。G1 把堆分成多个 Region每次只回收一部分 Region避免一次 STW 扫全堆。Q4-Xms 和 -Xmx 为什么要设一样话术为了避免 JVM 运行时动态缩扩容。如果 Xms1g, Xmx4gJVM 启动只占 1g后面需要更多堆时会向操作系统申请这个过程比较耗性能。两个值设成一样启动时直接拿满运行稳定。Q5OOM 怎么排查话术加-XX:HeapDumpOnOutOfMemoryError参数OOM 时自动 dump 堆文件。然后用 MAT 或 JProfiler 分析第一步看大对象怀疑列表第二步看 GC Roots 路径找到谁引用着它第三步看线程栈找到是在哪个业务代码里创建的。最常见的情况是一个不断增长的集合List/Map、忘记关闭的流或连接、ThreadLocal 没用 remove。Q6元空间和永久代有什么区别话术永久代是 JDK 7 及之前的方案在堆内存里划了一块固定大小类多了容易 OOM。JDK 8 改成了元空间用的是本地内存堆外默认只受物理内存限制不会轻易 OOM。改的原因就是永久代大小难估换元空间更灵活。Q7STW 是什么怎么减少话术STWStop The World指 GC 时所有业务线程暂停。减少 STW 的方式① 用 G1/ZGC 这种并发回收器大部分阶段跟业务线程一起跑② 调 MaxGCPauseMillis 控制 G1 的停顿目标③ 合理设置堆大小避免 GC 太久④ 减少 Full GC 次数YGC 通常几十毫秒Full GC 可能秒级。Q8jstack 和 jmap 分别用在什么场景话术jstack 看线程哪个线程卡住了、在等什么锁、是不是死锁了。jmap 看堆堆占用、各代大小、还能 dump 堆文件。通常场景CPU 飙高 → jstack 找线程看是不是 GC 线程在狂跑OOM → jmap dump 分析堆。Q9对象什么时候直接进老年代话术三种情况① 大对象超过-XX:PretenureSizeThreshold② 动态年龄判定Survivor 区放不下③ 长期存活的对象超过-XX:MaxTenuringThreshold次 YGC 还没回收。如果发现大量对象直接进老年代检查是不是大对象阈值设得偏低。Q10JVM 调优有哪些常用参数组合话术常用组合是三件套① 堆大小-Xms -Xmx -Xmn② 垃圾回收器-XX:UseG1GC / UseParallelGC / UseZGC③ GC 日志-Xlog:gc*。实际项目中的模板通常是堆设物理内存的 60-70%保留一部分给元空间和系统G1 设目标停顿 200ms。先跑一两天看 GC 日志再微调。八、一句话速记调优先看监控没数据不改参数 GC 要控频率和停顿 YGC 几秒一次、几十毫秒 → 正常 Full GC 频繁 → 排查泄漏或调参 微服务标配G1 堆的 60% GC 日志 OOM 必加 HeapDump不然死了都不知道怎么死的