java8 JVM
一、核心垃圾回收算法
Java 8 的收集器都基于以下算法组合:
-
标记-清除 (Mark-Sweep):标记活对象,清除未标记对象。会产生内存碎片。
-
标记-整理 (Mark-Compact):标记后,把活对象向一端移动,解决碎片,但 STW 时间更长。
-
复制 (Copying):将存活对象从一块内存复制到另一块,清空原区域。用于年轻代,效率高但浪费一半空间(实际通过 Eden + Survivor 优化)。
-
分代收集:根据对象存活周期,年轻代用复制算法,老年代用标记-清除/整理。
-
永久代被移除,换成元空间(Metaspace)存放类元数据,使用本地内存
三、Java 8 可用的垃圾收集器
1. Parallel GC(吞吐量优先收集器)
1.1吞吐量 = 用户代码运行时间 / (用户代码运行时间 + GC 暂停时间)
1.2 模式: 多线程并行执行 Minor GC 和 Full GC,仍会 STW。
| 区域 | 算法 |
|---|---|
| 年轻代 | 复制 (Parallel Scavenge) |
| 老年代 | 标记-整理 (Parallel Old) |
1.3 Java 8 在 Server 类机器上的默认收集器(2 GB 内存以上、多核)。
适用:注重吞吐量(业务处理时间 / (业务处理时间+GC时间))的后台批处理、科学计算。
1.4 空间管理
-
年轻代:一块 Eden + 两块 Survivor(通常叫 S0/S1 或 From/To)
-
老年代:一整块连续空间
-
元空间(Metaspace,不在堆内)
其中,年轻代内部这三块的大小是通过 -XX:SurvivorRatio 参数配置的,例如默认 8,表示 Eden : S0 : S1 = 8 : 1 : 1
1.5 垃圾回收过程
整个过程主要分两个阶段:Young GC(年轻代回收) 和 Full GC(全堆回收)。
(1)Young GC —— 年轻代回收(仅 Eden + From Survivor)
触发条件:当 Eden 区被新对象填满,无法再分配时,触发一次 Young GC。
过程(复制算法):
-
标记存活对象:从 GC Roots 出发,找出 Eden 和当前 From Survivor(S0)中所有存活的对象。
-
复制到目标区域:
-
将存活对象复制到 To Survivor(S1) 中,对象的“年龄”会 +1。
-
如果对象年龄达到
-XX:MaxTenuringThreshold(默认 15),或者 To Survivor 空间不足,就直接复制到 老年代。
-
-
清空源区域:
-
复制完成后,整个 Eden 和 From Survivor 被清空,所有对象要么去了 To Survivor,要么晋升到老年代。
-
-
角色互换:
-
此时,之前的 To Survivor (S1) 变成了新的 From Survivor,里面装着存活下来的对象。
-
之前的 From Survivor (S0) 变成了新的 To Survivor,完全为空。
-
Eden 区重新变成一整块空闲空间。
-
关键点:
Young GC 只处理年轻代,老年代不参与(但需要通过卡表记录老年代对年轻代的引用)。这个过程是 Stop-The-World 的,所有应用线程暂停,由多条 GC 线程并行进行复制和清理。
(2)Old GC / Full GC —— 老年代 + 年轻代一起回收
触发条件:
-
老年代空间不足以容纳晋升的对象(Young GC 时发现)。
-
或者通过
System.gc()等显式触发。 -
元空间不足也可能触发。
过程(标记-整理算法,Parallel Old):
-
标记存活对象:从 GC Roots 出发,标记整个堆(年轻代 + 老年代)所有存活对象。
-
计算整理位置:将存活对象向老年代的一端滑动,消除内存碎片。
-
更新引用:所有指向移动后对象的指针都需要更新。
-
回收空间:整理完成后,老年代剩余空间是连续的一大块,年轻代被完全清空(因为 Full GC 会处理年轻代)。
Full GC 也是 Stop-The-World 的,由多线程并行执行,但时间通常比 Young GC 长得多,会严重影响响应时间。
1.5 关键参数:
2.G1 GC(垃圾优先收集器)
2.1 参数:
-XX:+UseG1GC 启用(java9开始才是默认gc收集器),大内存(通常大于 4GB)且要求低延迟(可预测的停顿时间)的场景非常适用
-XX:MaxGCPauseMillis 控制每次停顿时间在设置的这个时间之内,JVM 会尽全力去达成这个目标,但不提供任何保证
2.2 模式:传统的垃圾收集器(如 Serial, Parallel, CMS)将堆内存划分为连续的三大块:年轻代(Eden + 2个 Survivor)和老年代(Old),G1将堆划分为多个大小相等的 Region,每个 Region 可以在逻辑上扮演 Eden、Survivor、Old 或 Humongous(大对象)区域,不严格要求连续分代,优先回收价值最大的 Region(垃圾最多),以可预测的停顿时间为目标。
2.3 收集过程 (GC Phases)
G1 的垃圾收集主要分为三种模式:
-
Young GC (年轻代收集):
-
触发条件: Eden 区域被占满时。
-
过程: 这是一个完全 Stop-The-World(STW,暂停所有用户线程)的过程。G1 会将 Eden 区存活的对象复制到 Survivor 区;如果有些对象年龄足够大,或者 Survivor 区空间不足,则直接晋升到 Old 区。
-
结果: 清空 Eden 区,重新计算下次 Young GC 的 Eden Region 数量。
-
-
Concurrent Marking (并发标记):
-
触发条件: 当堆内存占用率达到阈值(
-XX:InitiatingHeapOccupancyPercent,默认 45%)时触发。 -
过程:
-
初始标记 (Initial Mark, STW): 伴随一次 Young GC,标记从 GC Roots 直接可达的对象。
-
并发标记 (Concurrent Mark): 和用户线程并发执行,遍历对象图找出所有存活对象。
-
最终标记 (Remark, STW): 处理并发阶段用户线程修改导致的遗漏。
-
清理 (Cleanup, STW/并发): 统计各 Region 的存活对象比例,完全为空的 Region 直接被回收。
-
-
-
Mixed GC (混合收集):
-
触发条件: 并发标记结束后,G1 知道哪些 Old Region 里的垃圾最多。
-
过程: 同样是 STW 的复制算法。它不仅会收集所有的年轻代 Region,还会根据设定的停顿时间目标,挑选一部分垃圾最多的老年代 Region 进行回收。这就是 G1 能够控制停顿时间并有效处理老年代碎片的原因。
-
2.4 为什么 Java 8 推荐在大堆使用 G1
G1 有几个显著优势:
-
无内存碎片: CMS 基于“标记-清除”算法,容易产生内存碎片,最终导致 Full GC。G1 在 Region 之间使用的是“复制”算法,局部看也是“标记-整理”,有效避免了碎片问题。
-
停顿时间可控: CMS 只能控制并发线程数,无法精准控制 STW 时间。G1 允许用户指定目标停顿时间。
-
内存占用更均匀: 化整为零的 Region 设计让内存在动态分配时更加灵活。
3. Serial GC(串行收集器)
单线程,所有 GC 工作都暂停应用线程(STW);单核 CPU、内存几百 MB 的小应用适用,简单高效;暂停时间长,多核环境浪费资源。
4. CMS GC(并发标记清除,低延迟)
致力于低延迟,在 Java 9 已被标记废弃,Java 14 移除(Java 8 仍可稳定使用)。
四、如何选择与调优
4.1 选择
| 目标 | 推荐收集器 | 常用参数 |
|---|---|---|
| 高吞吐(后台批处理 / 数据计算) | Parallel | -XX:MaxGCPauseMillis |
| 低延迟(Web 接口 / 交易系统) | CMS | -XX:CMSInitiatingOccupancyFraction=70 等 |
| 大堆 + 可预测停顿 | G1 | -XX:MaxGCPauseMillis=200、-XX:ParallelGCThreads |
通用建议:
-
尽量让朝生夕死的对象在年轻代被回收,避免提前晋升导致老年代膨胀。
-
合理设置堆大小、元空间上限,避免频繁 Full GC。
-
警惕大对象
-
防范内存泄漏: 及时清理长生命周期容器(如静态
Map、ThreadLocal、自定义缓存)中的无用引用。如果老年代的使用率呈阶梯状持续上升,通常就是内存泄漏的信号。 -
开启并分析 GC 日志: 这是排查问题的唯一真理。通过日志观察 GC 发生的频率、每次回收前后的内存大小以及具体的停顿时间。
-
对症下药: 调优前先定位核心矛盾。是 Young GC 过于频繁导致 CPU 飙高?还是 Full GC 停顿太长导致接口超时?针对具体症状调整。
-
控制变量法: 一次只修改一个参数,调整后必须通过压测或灰度环境的监控数据来验证效果。如果改了参数没效果,甚至变差了,立刻回滚。
4.2. 常用 JVM 参数(能配、能调)
1. 堆内存与基本分配 (核心必配)
堆内存的合理分配是 JVM 稳定的基础。通常建议将 -Xms 和 -Xmx 设置为相同值。
-
-Xms<size>-
作用: 设置 JVM 启动时的初始堆内存大小。
-
示例:
-Xms4g(初始堆为 4GB)
-
-
-Xmx<size>-
作用: 设置 JVM 可使用的最大堆内存大小。
-
示例:
-Xmx4g(最大堆为 4GB) -
最佳实践:
-Xms和-Xmx配置相同,避免运行时动态扩容/缩容带来的性能开销。
-
-
-XX:MetaspaceSize=<size>/-XX:MaxMetaspaceSize=<size>-
作用: 设置元空间 (Metaspace,存放类元数据) 的初始和最大大小。(注意:Java 8 移除了 PermGen 永久代,改为 Metaspace)
-
示例:
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -
建议: 默认情况下 Metaspace 会使用本地内存并动态扩容,为防止其无限扩张导致系统物理内存耗尽,强烈建议明确指定上限,并保持两者一致。
-
2. Java 8 垃圾收集器选择 (根据场景二选一)
Java 8 默认的垃圾收集器是 Parallel GC,但在大内存下 G1 表现更好。
选项 A:高吞吐量 (批处理/后台计算 - Java 8 默认)
-
-XX:+UseParallelGC-
作用: 启用 Parallel Scavenge (年轻代) + Parallel Old (老年代) 收集器。追求最大 CPU 利用率,不在乎偶尔较长的卡顿。
-
选项 B:低延迟/可控停顿 (Web 服务/微服务 - Java 8 强烈推荐)
-
-XX:+UseG1GC-
作用: 启用 G1 (Garbage-First) 收集器。适合大内存 (一般大于 4GB) 且要求较短 STW 停顿时间的应用。
-
-
-XX:MaxGCPauseMillis=<N>-
作用: (仅 G1) 设置期望的最大 GC 停顿时间(毫秒)。
-
示例:
-XX:MaxGCPauseMillis=200 -
注意: 这是一个“软目标”,G1 会尽力满足,但不能保证绝对不超。设置过小会导致 GC 极其频繁,降低吞吐量。
-
3. OOM 防御与 Dump 获取 (极其重要)
当系统发生 OutOfMemoryError 时,必须保留案发现场,这些参数是救命稻草。
-
-XX:+HeapDumpOnOutOfMemoryError-
作用: 当 JVM 发生 OOM 时,自动生成 Heap Dump (.hprof) 文件。
-
-
-XX:HeapDumpPath=<path>-
作用: 指定 OOM 时生成的 Dump 文件的保存路径。
-
示例:
-XX:HeapDumpPath=/data/logs/jvm/heapdump.hprof -
注意: 确保运行 JVM 的用户对该目录有写入权限,并且所在磁盘有足够空间容纳整个堆。
-
-
-XX:+ExitOnOutOfMemoryError-
作用: 一旦发生 OOM,JVM 进程立刻退出。
-
场景: 在容器化 (Kubernetes/Docker) 环境中非常有用。OOM 后立刻退出,由容器编排工具快速拉起新实例,避免服务处于假死状态。
-
4. GC 日志参数 (Java 8 必备)
如果你不知道系统卡在哪,第一步就是看 GC 日志。以下是 Java 8 专属的日志配置方式:
-
-XX:+PrintGCDetails(打印详细 GC 发生时的内存变化信息) -
-XX:+PrintGCDateStamps(打印 GC 发生的绝对时间戳,方便与业务日志对齐) -
-Xloggc:/path/to/gc.log(指定 GC 日志输出的物理路径) -
-XX:+UseGCLogFileRotation(开启日志滚动,防止单个日志文件把磁盘撑爆) -
-XX:NumberOfGCLogFiles=10(保留的滚动文件个数) -
-XX:GCLogFileSize=50M(单个日志文件大小,达到 50M 后自动滚动生成新文件)
4. GC 日志分析(能看懂,能定位)
-
读懂每一行 Minor GC / Full GC 日志,知道各数字(回收前、回收后、总大小)的含义
-
能从日志判断是否正常:年轻代回收是否过于频繁、对象晋升速度是否异常、Full GC 为何触发
-
利用在线工具(如 gceasy)快速可视化分析
5. 常见问题排查能力(能解决实际问题)
必须能独立处理以下典型场景:
-
频繁 Full GC:是内存泄漏导致老年代持续增长?还是大对象直接进入老年代?还是元空间不足?
-
CPU 飙高:可能是并发 GC 线程过多、或频繁 Young GC
-
OOM 异常:
-
Java heap space:堆内存不足,dump 分析大对象 -
GC overhead limit exceeded:GC 占用了 98% 以上 CPU 却只回收不到 2% 堆,几乎内存耗尽 -
Metaspace:动态类加载或类加载器泄漏
-
-
内存泄漏排查:通过
jmap -histo、jmap -dump+ MAT / JProfiler 分析 GC Root 引用链 -
CMS Concurrent Mode Failure:并发收集时老年代不足,退化为 Serial Old,参数如何调整
6. 工具链(会用、能组合)
-
命令行:
jps、jstat -gc、jmap、jstack、jcmd -
可视化:VisualVM、JConsole、MAT(内存分析)、GCViewer
-
在线生产慎用
jmap -dump时的 Full GC 影响,掌握安全 dump 方式
更多推荐

所有评论(0)