在当前大环境背景下,掌握 jvm 的一些概念与调优必不可少,jvm 参数也是重中之重。
随着JDK 9的模块化革新以及如今JDK 11、17这些长期支持版本的普及,一个重要的变化悄然发生:G1垃圾回收器已正式成为默认选择,ZGC等新一代回收器也崭露头角。
原有的一些调优方式已不适用于当前 G1,这不仅仅是更换一个回收器那么简单,其内在的架构思想与工作机制,与传统的Parallel、CMS等已截然不同,但是还有很多只会八股文的开发者套用旧的概念,用旧的参数,没有真正的理解垃圾回收器的变迁。
本篇博客将划分那些过时的概念,从实战角度出发,带你系统性地梳理从JDK 8到JDK 17的JVM核心参数与垃圾回收器的演进之路,帮助你真正理解不同版本、不同回收器下的调优逻辑,帮助你建立新时代的调优思维。
我们经常把 JVM 调优挂在嘴边,好像不调优系统就要垮了一样,大多数是为了调优而调优。
我个人理解的调优并非秀技,在我看来需要调优的时机无非就2点:
理解 JVM 参数的第一步是掌握 Java 堆内存的传统布局方式,这也是大多数“祖传”配置的来源。首先我们来回顾一下 JDK 8 的内存布局,毕竟这款垃圾回收器的使用占据了我们工作当中的大部分时间。
Java 堆区通常划分为年轻代 和老年代。年轻代又可细分为 Eden 区和两个 Survivor 区(通常称为 From 和 To 区,简称为 S0, S1)

新生代与老年代比例:
通过 -XX:NewRatio 参数控制,默认值为 2,表示新生代占整个堆的 1/3,老年代占 2/3 。例如,设置 -XX:NewRatio=4 表示新生代占 1/5,老年代占 4/5 。当应用中长生命周期对象偏多时,可调整此参数来优化 。
Eden 与 Survivor 比例:
通过 -XX:SurvivorRatio 参数控制,默认值为 8 。这表示 Eden 区与一个 Survivor 区的比例是 8:1,即每个 Survivor 区占年轻代的 1/10 。例如,-XX:SurvivorRatio=8 配置下,两个 Survivor 区与一个 Eden 区的比例为 2:8 。
年轻代绝对大小:
除了比例调整,还可以使用 -Xmn 参数直接设置年轻代的大小 。Sun 官方推荐将年轻代配置为整个堆的 3/8 。
初始分配:几乎所有新对象都在 Eden 区创建 。
Minor GC:当 Eden 区满时,触发 Minor GC 。不再被引用的对象被销毁,存活对象被移动到其中一个 Survivor 区 。
Survivor 区周转:每次 Minor GC 时,存活对象在 Survivor 区间复制,每经历一次 Minor GC,对象年龄加 1 。
晋升老年代:当对象年龄达到 -XX:MaxTenuringThreshold 设置的值(默认 15)时,会从年轻代晋升到老年代(还有大对象,内存对象年龄超过一定比例)。
如何确认服务的内存大小
在以前工作的时候,在 JDK8 的版本确认一个服务最合适的内存大小,我就是在峰值时通过年轻代的内存使用率,根据新老年代的比例划分,反推出合适的内存大小,在适当的上浮一些内存就行。
JDK 7 及以前:使用 -XX:PermSize 和 -XX:MaxPermSize 设置永久代大小 。
JDK 8+:永久代被元空间取代,使用 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 参数控制 。通常建议设置为 256M-512M 。
JDK 8 的永久代(PermGen) 位于Java堆内,受堆大小限制,容易发生java.lang.OutOfMemoryError: PermGen space 错误。
JDK 8之后,方法区被移至本地内存中的元空间(Metaspace)。
存储位置与内存管理:元空间使用本地内存(Native Memory)。这意味着它可以使用的总内存约等于系统总内存,从而极大减少了因为类元数据过多而导致的内存溢出问题。元空间默认无上限,但可通过 -XX:MaxMetaspaceSize 限制。
性能与垃圾回收:元空间内存由操作系统管理,类的元数据生命周期与类加载器一致。当类加载器被回收时,其对应的元数据所占用的整个内存块会被释放,效率更高。
元空间溢出
虽然 Metaspace(元空间)取代了之前的 PermGen(永久代)来存储类的元数据,但是Metaspace 是本地内存的一部分,这意味着它不受到Java堆内存的限制,理论上可以无限扩展,实际可用空间受到系统可用内存的限制。
元空间溢出通常由以下几个原因引起:
类、方法的大量加载:如果你的应用程序加载了大量的类或方法,尤其是那些动态生成的类(如使用CGLIB、ASM等库),这些都会消耗大量的元空间。
大量使用反射:频繁使用反射创建类对象也会导致元空间快速消耗。
第三方库或框架:一些第三方库或框架可能会在运行时生成大量的类或方法,例如Hibernate、Spring等。
解决方案:
bash`-XX:MetaspaceSize=256m` 元空间初始大小 256m `-XX:MaxMetaspaceSize=512m` 元空间最大大小 512m
这里将最大元空间设置为512MB。默认情况下,MaxMetaspaceSize 是无界的,但实际上JVM会尝试根据系统可用内存自动调整。
代码优化:审查代码以减少不必要的类加载和反射使用。例如,避免动态生成大量类,或者优化反射的使用方式。
使用类数据共享(CDS):Java 9引入了类数据共享(Class Data Sharing, CDS),这可以帮助减少启动时加载类和方法的内存需求。你可以使用 -Xshare:dump 生成CDS归档文件,然后在其他JVM中使用 -Xshare:on 来加载这些归档文件。
监控和诊断:使用工具如VisualVM、JConsole或jcmd(jcmd <pid> VM.metaspace)来监控元空间的使用情况,找出元空间溢出的具体原因。
限制类的加载:对于某些情况下,可以通过设置系统属性来限制某些类的加载,例如使用 -XX:PermSize 和 -XX:MaxPermSize(虽然这些参数在JDK 8中已经废弃,因为永久代被移除,但在某些情况下仍然可以尝试使用它们来限制类的加载)。在JDK 11中,你可以通过限制第三方库的权限或使用更细粒度的安全策略来间接控制类的加载。
通过上述方法,你可以有效地管理和解决JDK 11中的元空间溢出问题
从JDK 9开始,G1 GC(Garbage-First) 成为默认垃圾回收器。
内存划分:G1将整个堆内存划分为多个大小相等(如1M-32M)的 Region。这些 Region 不再像 JDK 8 那样物理上固定分为 年轻代 和 老年代,而是逻辑上扮演 Eden、Survivor、Old Region 等角色。
回收策略:G1通过计算,优先回收那些垃圾最多(Garbage-First)的 Region,力求在有限的停顿时间内获得最高的回收效益。这也导致了调优思路的转变:在G1中,你通常不需要也不能手动设置 -Xmn(年轻代大小)或 -XX:SurvivorRatio(Eden与Survivor比例),因为这些区域是动态的。

核心特点
-XX:MaxGCPauseMillis 目标停顿时间,G1会尽量满足JDK 11引入的 ZGC 和 Shenandoah GC,其内存布局观念更为超前。
ZGC:ZGC虽然仍有 Young 和 Old 的概念,但其核心是基于 染色指针 和 读屏障的并发处理 能力。它通过虚拟内存映射技术,在指针中存储额外信息来标记对象的状态,从而实现了TB级堆内存下依然能保持极低的停顿时间。
Shenandoah GC:Shenandoah GC也采用了 Region 化布局,但其特点是并发整理做得非常出色。它通过 Brooks Pointer 技术,使得在对象移动时,应用线程几乎无感知,从而实现了与堆大小无关的低停顿。
字符串去重:G1 GC引入了字符串去重功能(通过 -XX:+UseStringDeduplication 开启)。它会自动识别并合并堆中重复的 String 对象内部的 char[] 数组,从而节省内存。
压缩指针优化:在64位JVM上,为了节省内存,指针压缩默认是开启的。JDK的发展中也在不断优化压缩指针的策略,使其在更大的堆地址范围内也能有效工作,进一步降低了内存开销。
对于在Docker等容器中运行的Java应用,JDK 9及之后版本增强了对容器环境的支持。
JDK 8的问题:JDK 8 的 JVM 无法感知容器设置的内存限制,它看到的是宿主机的物理内存。这可能导致JVM分配内存超过容器限制而被操作系统强制终止。
JDK 9+的改进:JDK 9 引入了 -XX:+UseContainerSupport(后续版本已默认开启)。这个参数让JVM能够读取Cgroup的限制,并以此为依据来设置堆大小等参数。你还可以使用 -XX:MaxRAMPercentage(例如 -XX:MaxRAMPercentage=75.0)来指定JVM最大堆内存占容器可用内存的百分比,这使得在容器中配置内存更加灵活和准确。
内存布局的转变
JDK 8:需要精细调整分代比例(NewRatio、SurvivorRatio)
JDK 11+:G1/ZGC 等回收器自主管理内存分区,开发者只需设定总体堆大小和暂停时间目标
随着 JDK 版本的演进,垃圾回收器也发生了显著变化,以下是各版本的主要回收器及其关键参数。
| 回收器 | 启用参数 | 特点与适用场景 |
|---|---|---|
| Parallel GC | -XX:+UseParallelGC | JDK 8 默认回收器,注重吞吐量 |
| CMS | -XX:+UseConcMarkSweepGC | 低暂停时间,适用于响应敏感型应用 |
| G1 | -XX:+UseG1GC | 兼顾吞吐量与暂停时间,大内存首选 |
Parallel GC 关键调优参数:
-XX:ParallelGCThreads:并行线程数-XX:MaxGCPauseMillis:最大GC停顿时间目标CMS 关键调优参数:
-XX:CMSInitiatingOccupancyFraction=70:设定老年代使用率达到多少时触发 CMS 回收
-XX:+UseCMSInitiatingOccupancyOnly:强制使用设定的阈值,而非 JVM 自适应
-Xmn:一般设置为堆内存的 1/3,尤其是配置了 ParallelGCThreads 后必须配置此参数
注意
CMS 是关注低停顿的垃圾回收器,启用参数为 -XX:+UseConcMarkSweepGC,在 JDK9 已弃用(Deprecated) ,JDK14+ 移除,建议迁移至 G1
从 JDK 9 开始,G1 垃圾回收器取代 Parallel GC 成为默认回收器,这标志着 JVM 调优思路的重大转变 。
G1 关键参数:
-XX:MaxGCPauseMillis=200 设定期望的GC最大停顿时间目标,默认200。G1会尽力实现但不保证。G1可能通过调整年轻代大小等手段尝试达成目标,调优中设置低延迟的主要手段,但设置过低可能牺牲吞吐量。
-XX:G1MixedGCCountTarget=<N> 设定在混合回收阶段中,回收老年代Region所需的次数,默认值是8。通过分多次回收老年代Region,控制单次停顿时间,这样可以让系统不至于单次停顿时间过长。也是调整 低延迟 的一把好手,将一次较长的停顿拆分为多次较短停顿。
-XX:G1HeapRegionSize=8m:设定每个Region的大小(1MB-32MB,2的N次幂)。影响大对象判断(超过Region一半即认为大对象)和内存分配。调整可应对 Humongous 对象过多或分配碎片问题。通过调节此参数的大小,去 平衡吞吐量/低延迟,其参数会影响内存分配效率和碎片情况。
-XX:G1ReservePercent:G1 为了保留一些空间用于年代之间的提升,默认值是堆空间的 10%。因为大量执行回收的地方在年轻代(存活时间较短),所以如果你的应用里面有比较大的堆内存空间、比较多的大对象存活,这里需要保留一些内存,作为空闲空间缓冲,防止晋升失败。低延迟: 降低因晋升失败导致 Full GC 的风险,提升稳定性。
-XX:InitiatingHeapOccupancyPercent=45 设定触发并发标记周期的老年代占用堆的百分比阈值(默认45%)。G1也可自适应调整此阈值。可以提高 吞吐量/低延迟,过早启动标记会增加GC开销(吞吐量),过晚则可能引发Full GC(严重影响延迟),根据业务可以适当均衡的倾斜。
-XX:ParallelGCThreads 是 JVM 中用于STW阶段设置并行垃圾回收线程数的关键参数,它控制在执行垃圾收集(如 Young GC)时参与并行工作的线程数量。默认情况下,JVM 会根据 CPU 核心数自动计算该值:当 CPU 核心数小于等于 8 时,线程数等于核心数;当 CPU 核心数大于 8 时,线程数按公式 8 + (CPU 核心数 - 8) * 5/8 计算。此参数可以提升 吞吐量:并行GC线程数影响STW阶段的回收速度。
提示
该参数广泛用于并行 GC 算法(如 Parallel Scavenge、G1、CMS),通过调整线程数优化性能。
需要显式设置的场景:
jstat -gc 确认)。推荐值与配置建议
通用规则:线程数不超过可用 CPU 核心数的 5/8,推荐值范围为 max(2, min(CPU 核心数 * 5/8, 16))。例如:
容器化环境推荐值:
| 容器核数 | 推荐值 | 建议上下界 |
|---|---|---|
| 2 | 2 | 1~2 |
| 4 | 4 | 2~4 |
| 8 | 8 | 4~8 |
| 16 | 13 | 8~16 |
| 32 | 23 | 16~32 |
| 64 | 43 | 32~64 |
-XX:ConcGCThreads: 并发线程数,默认约 -XX:ParallelGCThreads/4,-XX:ParallelGCThreads 和 -XX:ConcGCThreads 是 JVM 中用于配置垃圾回收器线程数的两个重要参数,它们分别控制 并行垃圾回收阶段 和 并发垃圾回收阶段 的线程数量,需要根据 CPU 核心数和应用负载平衡设置,避免线程过多导致资源竞争或过少影响回收效率。在容器中需显式设置这两个参数,避免 JVM 基于宿主机核心数计算过多线程,导致资源争抢。合理配置这两个参数可以显著优化垃圾回收性能,减少应用停顿时间,提高系统吞吐量。
-XX:G1HeapWastePercent=<N> 设置堆废物百分比(即可回收空间占整堆的比例,默认5%)。当可回收空间超过此比例,G1才会启动混合回收。控制 吞吐量:影响GC触发时机和内存回收效率。
-XX:G1MixedGCLiveThresholdPercent=<N> 设定老年代Region中存活对象占比的阈值(默认85%)。存活对象比例低于此值的Region才会被考虑纳入混合回收的收集集合(CSet)。 吞吐量:优先回收垃圾多的Region(Garbage First),提高单次GC效率。
-XX:+G1PrintRegionLivenessInfo:这个参数需要和 -XX:+UnlockDiagnosticVMOptions 配合启动,打印 JVM 的调试信息,每个 Region 里的对象存活信息。
-XX:+G1SummarizeRSetStats:这也是一个 VM 的调试信息。如果启用,会在 VM 退出的时候打印出 Rsets 的详细总结信息。如果启用 -XX:G1SummaryRSetStatsPeriod 参数,就会阶段性地打印 Rsets 信息。
-XX:+G1TraceConcRefinement:这个也是一个 VM 的调试信息,如果启用,并行回收阶段的日志就会被详细打印出来。
-XX:+GCTimeRatio:这个参数就是计算花在 Java 应用线程上和花在 GC 线程上的时间比率,默认是 9,跟新生代内存的分配比例一致。这个参数主要的目的是让用户可以控制花在应用上的时间,G1 的计算公式是 100/(1+GCTimeRatio)。这样如果参数设置为 9,则最多 10% 的时间会花在 GC 工作上面。Parallel GC 的默认值是 99,表示 1% 的时间被用在 GC 上面,这是因为 Parallel GC 贯穿整个 GC,而 G1 则根据 Region 来进行划分,不需要全局性扫描整个内存堆。
-XX:+UseStringDeduplication:手动开启 Java String 对象的去重工作,这个是 JDK8u20 版本之后新增的参数,主要用于相同 String 避免重复申请内存,节约 Region 的使用。
-XX:MaxGCPauseMills:预期 G1 每次执行 GC 操作的暂停时间,单位是毫秒,默认值是 200 毫秒,G1 会尽量保证控制在这个范围内。
-XX:G1NewSizePercent=5, -XX:G1MaxNewSizePercent=60 : 新生代 region 最小占整堆的 5%,最大 60%,超出则触发新生代 GC
注意
使用 G1 时,不要设置 -Xmn 和 -XX:NewRatio 参数 ,因为这些会干扰 G1 的自适应区域管理机制。
JDK 11 开始引入了面向未来的低延迟回收器,在 JDK 17 中趋于成熟。
| 回收器 | 启用参数 | JDK 支持状态 | 核心特点 |
|---|---|---|---|
| ZGC | -XX:+UseZGC | JDK 11(实验版)→ JDK 15+(生产就绪)→ JDK 17+(正式产品) | 亚毫秒级暂停,支持 TB 级堆 |
| Shenandoah | -XX:+UseShenandoahGC | 需特定 JDK 发行版 → JDK 15+(生产就绪)→ JDK 17+(正式产品) | 低暂停时间,与用户程序并发执行 |
ZGC 关键参数:
-XX:ConcGCThreads:设置并发 GC 线程数
-XX:ZAllocationSpikeTolerance=2.0 : 分配尖峰容忍度
-XX:+ZUncommit:启用未使用内存归还(默认开启)
Shenandoah 关键参数:
-XX:ShenandoahGCMode=normal/satb : 默认模式 或 iu (实验性增量更新模式)
-XX:ShenandoahGCHeuristics=adaptive : 启发式策略
JVM参数主要分为以下三类:
| 参数类型 | 说明 | 示例 |
|---|---|---|
| 标准参数 | 功能稳定,随JDK版本变化小。 | -help, -version |
-X参数 | 非标准参数,后续版本可能变更。 | -Xms, -Xmx |
-XX参数 | 用于JVM调优和Debug,使用最频繁。 | -XX:+UseG1GC |
这些是调优的基础,直接影响GC频率和性能。
| 参数 | 说明 | 建议与示例 |
|---|---|---|
-Xms | 堆内存初始大小。设为与 -Xmx 相同以避免运行时调整。 | -Xms2g |
-Xmx | 堆内存最大大小。通常设为系统可用内存的50%-80%。 | -Xmx4g |
-Xmn | 年轻代大小。Sun官方推荐为整个堆的3/8。 设置不当可能影响GC效率。G1回收器通常不设。 | -Xmn1g |
-XX:NewRatio | 年轻代与老年代的比值。 例如 -XX:NewRatio=2 表示老年代是年轻代的2倍。 | -XX:NewRatio=2 |
-XX:SurvivorRatio | Eden区与一个Survivor区的容量比值。 例如 -XX:SurvivorRatio=8 表示 | Eden:S0:S1=8:1:1。 |
-XX:MetaspaceSize | 元空间初始大小,达到该值会触发Full GC。 | -XX:MetaspaceSize=256m |
-XX:MaxMetaspaceSize | 元空间最大大小,默认无限制。 | -XX:MaxMetaspaceSize=512m |
-Xss | 每个线程的栈大小。减小它能创建更多线程,但可能栈溢出。 | -Xss1m |
选择合适的垃圾回收器至关重要。
| 回收器 | 启用参数 | 特点与适用场景 |
|---|---|---|
| Serial | -XX:+UseSerialGC | 单线程,简单高效,适用于单CPU或客户端小程序。 |
| Parallel | -XX:+UseParallelGC | JDK8默认,多线程高吞吐量,适合后台运算。 |
| CMS | -XX:+UseConcMarkSweepGC | 并发低停顿,适合Web等响应速度要求的应用。 |
| G1 | -XX:+UseG1GC | 兼顾吞吐量和低停顿,大堆首选,JDK9+默认。 |
| ZGC | -XX:+UseZGC | 极低停顿,适用于超大堆内存 |
提示
现在主流的还是 G1 垃圾回收器,对于多数企业来说,业务没有达到一定量级,G1 是首选。
这些参数是发现问题和分析问题的眼睛。
| 参数 | 说明 | 示例 |
|---|---|---|
-XX:+PrintGCDetails | 打印详细GC信息。 | -XX:+PrintGCDetails |
-XX:+PrintGCDateStamps | 打印GC发生的具体时间。 | -XX:+PrintGCDateStamps |
-Xloggc | =将GC日志输出到文件。 | -Xloggc:./gc.log |
-XX:+HeapDumpOnOutOfMemoryError | 在内存溢出时生成堆转储文件。 | -XX:+HeapDumpOnOutOfMemoryError |
-XX:HeapDumpPath | 指定堆转储文件的保存路径。 | -XX:HeapDumpPath=./java.hprof |
-XX:OnOutOfMemoryError | 发生OOM时执行指定脚本。 | -XX:OnOutOfMemoryError="jstack -l %p > /tmp/threaddump.txt" |
无论使用哪个版本的 JDK,以下参数都值得关注:
内存相关:
-Xms 和 -Xmx:设置堆内存的初始大小和最大值,建议将这两个值设置为相同,以避免运行时堆大小动态调整带来的性能波动。其大小通常设置为系统可用内存的 50% - 80%。
-XX:MetaspaceSize (通常256M) 和 -XX:MaxMetaspaceSize (通常512M) : 设置元空间,防止默认值过小导致 Full GC 或元空间无限扩容。
GC 日志与诊断:
强烈建议开启GC日志以便排查问题和优化性能
-XX:+PrintGCDetails -XX:+PrintGCDateStamps:打印详细的 GC 信息
-Xloggc:/path/to/gc.log:将 GC 日志输出到文件
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/:发生 OOM 时生成堆转储
以下是一个针对8核8G容器,配置目标:平衡吞吐量和延迟,适应突发流量
bashjava -Xms4g -Xmx4g \ # 堆内存固定为4G
-XX:+UseG1GC \ # 启用G1回收器
-XX:MetaspaceSize=256m # 元空间内存
-XX:MaxMetaspaceSize=512m # 元空间最大内存
-XX:MaxGCPauseMillis=150 \ # 设定最大停顿时间目标为150ms
-XX:G1HeapRegionSize=4m \ # 根据需求或建议[citation:3]设定Region大小
-XX:InitiatingHeapOccupancyPercent=40 \ # 稍提前启动并发标记
-XX:ConcGCThreads=2 \ # 设置并发标记线程数
-XX:G1MixedGCCountTarget=8 \ # 增加混合回收次数,拆分停顿
-XX:+PrintGCDetails -XX:+PrintGCDateStamps \ # 打印GC日志详情
-Xloggc:/path/to/gc.log \ # 输出GC日志到文件
-XX:+HeapDumpOnOutOfMemoryError \ # OOM时生成堆转储
-jar your-application.jar
配置目标:最大化吞吐量,允许较长停顿
sh# 基础配置
-XX:+UseG1GC
-Xms16g -Xmx16g
# G1优化配置
-XX:MaxGCPauseMillis=300 # 允许较长停顿
-XX:ParallelGCThreads=16 # 16核CPU
-XX:G1HeapRegionSize=8m # 较大Region减少开销
-XX:InitiatingHeapOccupancyPercent=50 # 标准触发阈值
-XX:G1NewSizePercent=30 # 更大的年轻代
通过开启的GC日志,你可以判断JVM是否健康:
GC频率:YoungGC频率通常几十秒一次较正常,FullGC几天一次甚至没有(G1目标就是避免FullGC)。
GC耗时:YoungGC时间通常应在几十毫秒,FullGC通常在几百毫秒。
内存回收效果:每次GC后,内存应回落到一个较稳定的水位。如果老年代使用率在FullGC后持续居高不下,可能存在内存泄漏。
本文作者:柳始恭
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!