AI全栈实战 | 1.7-01 JVM 内存与 GC:大对象为什么直接进老年代?G1 凭什么取代 CMS
上篇回顾1.6-02 把项目经验转化为面试弹药——STAR 法则 量化公式 三层深度模型 追问防御策略。本篇进入 Java 高阶第一章JVM。这是区分「会写 Java」和「懂 Java」的分水岭——线上 OOM 你能不能定位GC 抖动你能不能调一、开篇一场线上 OOM 事故某天凌晨收到告警订单服务 OOM 重启。运维 dump 了堆快照扔给开发。开发打开 MAT 一看一个HashMap占了 800MB堆才 2GBHashMap 里存的是订单缓存key 是订单 ID上线了一个新功能循环里往这个 Map 塞数据但忘了设过期时间订单量累计到 200 万条时堆撑爆了修复方案很简单换 Caffeine 设最大 size TTL。但定位过程花了 4 小时——因为开发不懂 jmap、不懂 MAT、不懂怎么看 GC 日志判断是不是内存泄漏。JVM 知识不是为了面试八股是为了线上出事时能在 1 小时内定位。本篇把 JVM 内存模型、GC 算法、收集器演进、排查工具链一次讲透。二、JVM 运行时数据区五大区域┌─────────────────────────────────────────────┐ │ JVM 运行时数据区 │ ├──────────┬──────────┬───────────────────────┤ │ 线程私有 │ 线程共享 │ │ ├──────────┼──────────┤ 方法区元空间 │ │ 虚拟机栈 │ 堆 │ (JDK8 元空间) │ │ 本地方法栈 │ │ │ │ 程序计数器 │ │ │ └──────────┴──────────┴───────────────────────┘2.1 各区域职责与异常类型区域线程存什么异常程序计数器私有当前线程执行的字节码行号唯一不会 OOM 的区域虚拟机栈私有栈帧局部变量、操作数栈StackOverflowError / OOM本地方法栈私有Native 方法栈帧同上堆共享对象实例、数组OOM最常见方法区/元空间共享类元信息、常量池、静态变量OOM: Metaspace2.2 堆的分代结构┌───────────────────────────────────┐ │ 堆 │ ├─────────┬───────────┬─────────────┤ │ 年轻代 │ │ 老年代 │ │ (1/3) │ │ (2/3) │ ├─────────┤ │ │ │ Eden │ S0 S1 │ │ │ (8/10) │ (1/10)(1/10)│ │ └─────────┴───────────┴─────────────┘年轻代老年代 1:2Eden:S0:S1 8:1:1。这是默认值G1 之后不再是固定比例。三、对象创建与内存分配路径new Object() ↓ 1. 类加载检查是否已加载 ↓ 2. 分配内存 - 指针碰撞内存规整Serial/ParNew - 空闲列表内存碎片CMS ↓ 3. 内存清零默认值 ↓ 4. 设置对象头哈希、GC 分代年龄、锁状态 ↓ 5. init 执行构造函数3.1 对象分配到哪里——分代分配规则规则说明优先 Eden绝大多数对象先分配到 Eden大对象直接老年代超过-XX:PretenureSizeThreshold的对象长期存活进老年代GC 年龄超过 15默认晋升动态年龄判断Survivor 中相同年龄对象大小超 Survivor 一半该年龄以上全部晋升空间分配担保Survivor 放不下时老年代做担保3.2 大对象为什么直接进老年代大对象如大数组、长字符串如果在 Eden 分配很快就要复制到 Survivor再复制到老年代——复制大对象很慢。直接放老年代省去多次复制。代价是老年代被大对象占住可能提前触发 Full GC。# 设置大对象阈值Serial/ParNew 有效G1 用 Region 自适应-XX:PretenureSizeThreshold1048576# 1MB四、GC 算法三种基础策略4.1 标记-清除Mark-Sweep1. 从 GC Roots 开始标记存活对象 2. 清除未标记对象缺点产生内存碎片分配大对象时找不到连续空间会提前触发 GC。4.2 复制Copying1. 将存活对象从 From 复制到 To 2. 清空 From 3. 交换 From/To优点无碎片分配快。缺点浪费一半空间。年轻代用此算法因为存活率低复制成本低。4.3 标记-整理Mark-Compact1. 标记存活对象 2. 存活对象向一端移动 3. 清理边界外空间优点无碎片。缺点移动对象慢STW。老年代用此算法。五、GC 收集器演进从 Serial 到 ZGC收集器作用域算法STW适用场景Serial年轻代复制长单核、小堆ParNew年轻代复制中多核、配合 CMSParallel Scavenge年轻代复制中吞吐量优先Serial Old老年代整理长单核Parallel Old老年代整理中吞吐量CMS老年代标记清除短4 步响应优先已废弃G1全堆Region整理短可控大堆推荐ZGC全堆染色指针10ms超大堆、低延迟5.1 CMS 的四步与致命缺陷1. 初始标记STW标记 GC Roots 直接引用 2. 并发标记从上一步扩散标记不 STW 3. 重新标记STW修正并发标记期间的变更 4. 并发清除清除未标记对象不 STW致命缺陷内存碎片标记清除不整理碎片多了大对象放不下提前 Full GCConcurrent Mode Failure并发标记期间老年代满了 → 退回 Serial Old 做 Full GC长时间 STW浮动垃圾并发清除阶段产生的新垃圾本轮回收不了5.2 G1 的 Region 设计解决了什么G1 把堆切成 2048 个等大 Region1~32MB每个 Region 可以动态充当 Eden/Survivor/Old/Humongous。┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐ │ E│ S│ O│ O│ E│ H│ O│ S│ E│ O│ EEden SSurvivor OOld HHumongous ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤ │ O│ E│ O│ O│ E│ O│ S│ O│ E│ O│ └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘解决了 CMS 的两大缺陷碎片问题G1 回收时把存活对象复制到空 Region复制算法天然无碎片STW 不可控G1 可以设-XX:MaxGCPauseMillis200G1 根据历史数据预测哪些 Region 回收价值最高垃圾最多优先回收这些——这就是Garbage First名字由来# G1 推荐配置JDK 9 默认-XX:UseG1GC-XX:MaxGCPauseMillis200-XX:G1HeapRegionSize16m5.3 ZGC亚毫秒级停顿ZGC 用染色指针 读屏障把 STW 压到 10ms且不随堆大小线性增长。适合 TB 级堆。JDK 15 后生产可用。六、GC 日志与排查工具链6.1 开启 GC 日志# JDK 8-XX:PrintGCDetails-XX:PrintGCDateStamps-Xloggc:/var/log/gc.log# JDK 11统一日志格式-Xlog:gc*info:file/var/log/gc.log:time,uptime,level,tags:filecount5,filesize10m6.2 在线诊断命令# 查看堆概况jmap-heappid# 查看对象统计按大小排序jmap-histopid|head-20# Dump 堆快照jmap-dump:formatb,fileheap.hprofpid# 查看 GC 统计jstat-gcutilpid100010# 每秒1次共10次6.3 MAT 分析堆快照打开heap.hprof关注Dominator Tree按对象保留大小排序找最大的Leak Suspects ReportMAT 自动分析疑似泄漏点Histogram按类统计对象数量排查路径Dominator Tree → 找大对象 → 查看它的 GC Root 引用链 → 找到为什么没被回收。6.4 arthas 在线诊断# 安装curl-Ohttps://arthas.aliyun.com/arthas-boot.jarjava-jararthas-boot.jar# 选择目标进程# 常用命令dashboard# 总览thread-n3# CPU 最高的 3 个线程jad ClassName# 反编译类watchClass method{params, returnObj}-x2# 观察方法出入参七、内存泄漏的常见模式模式原因典型场景集合不清理往 Map/List 塞数据不删缓存无 TTL、注册表不移除静态集合静态字段生命周期 JVM 生命周期static HashMap 累积线程泄漏线程不退出ThreadLocal 不 remove、线程池不 shutdown资源未关闭流/连接不 closeIO、DB 连接异常分支未关监听器未注销注册后不移除事件监听器累积八、JVM 参数调优常见组合# 中小项目4C8G-Xms4g-Xmx4g# 堆固定 4G避免动态扩缩-XX:UseG1GC# G1 收集器-XX:MaxGCPauseMillis200# 目标停顿 200ms-XX:HeapDumpOnOutOfMemoryError# OOM 时自动 dump-XX:HeapDumpPath/var/log/heap/# dump 路径-Xlog:gc*info:file/var/log/gc.log:time:filecount5,filesize10m# 大项目16C32G-XX:UseZGC# ZGCJDK 15-XX:ZCollectionInterval120# 主动 GC 间隔-XX:ConcGCThreads4# 并发 GC 线程数九、小结表模块关键点工具内存区域堆/栈/方法区/PC/本地栈jmap分代Eden→Survivor→OldjstatGC 算法复制/清除/整理GC 日志收集器G1 推荐、ZGC 前沿-XX:UseG1GC排查dump MAT arthasjmap/MAT/arthas