# JVM 和垃圾回收 ## 问题 1. JVM 内存模型是怎样的? 2. 垃圾回收有哪些算法?G1 和 CMS 的区别? 3. 什么是 Stop-The-World?如何减少 STW 时间? 4. 常见的 OOM 及解决方案? 5. JVM 参数如何调优? 6. 如何分析 GC 日志? --- ## 标准答案 ### 1. JVM 内存模型 #### **运行时数据区** ``` ┌─────────────────────────────────────┐ │ 方法区(Method Area) │ │ (类信息、常量、静态变量、JIT 代码) │ └─────────────────────────────────────┘ ┌─────────────────────────────────────┐ │ 堆(Heap) │ │ (对象实例、数组) │ │ ┌──────────┐ ┌──────────┐ │ │ │ 新生代 │ │ 老年代 │ │ │ │ Eden │ │ │ │ │ │ S0 │ │ │ │ │ │ S1 │ │ │ │ │ └──────────┘ └──────────┘ │ └─────────────────────────────────────┘ ┌─────────────────────────────────────┐ │ JVM 栈(Java Stack) │ │ (栈帧:局部变量、操作数栈、返回地址) │ └─────────────────────────────────────┘ ┌─────────────────────────────────────┐ │ 本地方法栈(Native Stack) │ │ (Native 方法调用) │ └─────────────────────────────────────┘ ┌─────────────────────────────────────┐ │ 程序计数器(PC Register) │ │ (当前执行的字节码指令) │ └─────────────────────────────────────┘ ``` --- ### 2. 垃圾回收算法 #### **标记-清除(Mark-Sweep)** **步骤**: 1. 标记存活对象 2. 清除未标记对象 **问题**: - 产生内存碎片 - 标记和清除效率都不高 --- #### **复制(Copying)** **步骤**: 1. 将存活对象复制到另一块内存 2. 清空当前内存 **优点**: - 无内存碎片 - 简单高效 **缺点**: - 内存利用率低(浪费一半) **适用**:新生代(Eden + S0 + S1) --- #### **标记-整理(Mark-Compact)** **步骤**: 1. 标记存活对象 2. 将存活对象向一端移动 3. 清理边界外的内存 **优点**: - 无内存碎片 **缺点**: - 移动对象成本高 **适用**:老年代 --- #### **分代收集(Generational)** **原理**: - **弱分代假说**:大多数对象朝生夕灭 - **新生代**:复制算法(Eden : S0 : S1 = 8 : 1 : 1) - **老年代**:标记-整理或标记-清除 --- ### 3. 垃圾收集器 #### **Serial 收集器** **特点**:单线程,STW **适用**:客户端应用、小内存 ```bash -XX:+UseSerialGC ``` --- #### **Parallel 收集器** **特点**:多线程,STW **适用**:后台任务、批处理 ```bash -XX:+UseParallelGC ``` --- #### **CMS(Concurrent Mark Sweep)** **特点**:并发收集,低停顿 **步骤**: 1. 初始标记(STW) 2. 并发标记 3. 重新标记(STW) 4. 并发清除 **问题**: - CPU 敏感 - 浮动垃圾 - 内存碎片 ```bash -XX:+UseConcMarkSweepGC ``` --- #### **G1(Garbage First)** **特点**: - **可预测停顿**:用户指定停顿时间 - **分区回收**:将堆划分为多个 Region - **优先回收垃圾多的 Region** **适用**:大堆内存(> 6GB)、多核 CPU ```bash -XX:+UseG1GC -XX:MaxGCPauseMillis=200 # 目标停顿时间 ``` --- #### **ZGC(Z Garbage Collector)** **特点**: - 并发整理(无内存碎片) - 停顿时间 < 10ms - 支持 TB 级堆内存 **适用**:超大内存、低延迟要求 ```bash -XX:+UseZGC ``` --- ### 4. OOM 及解决方案 #### **Java.lang.OutOfMemoryError: Java heap space** **原因**:堆内存不足 **解决**: ```bash # 增加堆内存 -Xms4g -Xmx4g # 使用 G1 收集器 -XX:+UseG1GC ``` --- #### **Java.lang.OutOfMemoryError: Metaspace** **原因**:方法区(元空间)不足 **解决**: ```bash # 增加元空间大小 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m ``` --- #### **Java.lang.OutOfMemoryError: GC overhead limit exceeded** **原因**:GC 频繁,但回收内存少 **解决**: - 增加堆内存 - 优化代码(减少对象创建) --- #### **Java.lang.StackOverflowError** **原因**:栈深度过大(递归太深) **解决**: ```bash # 增加栈大小 -Xss2m ``` --- ### 5. JVM 参数调优 #### **常用参数** ```bash # 堆内存 -Xms4g # 初始堆大小 -Xmx4g # 最大堆大小 -Xmn2g # 新生代大小 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m # GC 选择 -XX:+UseG1GC -XX:MaxGCPauseMillis=200 # GC 日志 -Xlog:gc*:file=/tmp/gc.log:time,tags:filecount=5,filesize=10m # OOM 时自动 Dump -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/ ``` --- #### **调优步骤** ``` 1. 监控 GC 频率和停顿时间 2. 分析 GC 日志 3. 调整堆内存和新生代比例 4. 选择合适的 GC 收集器 5. 优化代码(减少对象创建) 6. 压测验证 ``` --- ### 6. GC 日志分析 #### **工具** - **GCViewer**:可视化分析 - **GCeasy**:在线分析(https://gceasy.io) - **阿里 Arthas**:线上诊断 --- #### **GC 日志示例** ``` [GC (Allocation Failure) [PSYoungGen: 2048K->512K(2560K)] 2048K->1024K(9216K), 0.0023456 secs] [Full GC (Ergonomics) [PSYoungGen: 512K->0K(2560K)] [ParOldGen: 512K->512K(6656K)] 1024K->512K(9216K), [Metaspace: 5120K->5120K(1056768K)], 0.0234567 secs] ``` **解读**: - **GC**:Minor GC - **Full GC**:Major GC - **Allocation Failure**:新生代不足 - **Ergonomics**:自适应调整 - **2048K->512K(2560K)**:回收前 2048K,回收后 512K,总容量 2560K - **0.0023456 secs**:停顿时间 --- ### 7. 阿里 P7 加分项 **深度理解**: - 理解 G1 的 Mixed GC 和 Region 设计 - 理解 ZGC 的染色指针和读屏障 - 理解 JVM 的 JIT 编译和逃逸分析 **实战经验**: - 有处理线上 OOM 问题的经验 - 有 GC 参数调优的经验 - 有 GC 日志分析和优化经验 **架构能力**: - 能设计高吞吐量或低延迟的 JVM 方案 - 能设计 JVM 监控和告警体系 - 能制定 JVM 调优规范 **技术选型**: - 了解各种 GC 收集器的适用场景 - 了解 JDK 8/11/17/21 的 GC 改进 - 有 GraalVM 等新技术的使用经验