JVM 实战
GC 日志分析
摘录 GC 日志一部分(前部分为年轻代 gc 回收;后部分为 full gc 回收):
2016-07-05T10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)] 371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07 secs] 
2016-07-05T10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)] [ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68 secs]通过上面日志分析得出
- PSYoungGen、ParOldGen、PSPermGen 属于 Parallel 收集器 - PSYoungGen 表示 gc 回收前后年轻代的内存变化 
- ParOldGen 表示 gc 回收前后老年代的内存变化 
- PSPermGen 表示 gc 回收前后永久区的内存变化 
 
- young gc 主要是针对年轻代进行内存回收比较频繁,耗时短 
- full gc 会对整个堆内存进行回城,耗时长,因此一般尽量减少 full gc 的次数 
调优
- 1 基础故障处理工具 - 1.1 jps: 虚拟机进程状态工具 
- 1.2 jstat: 虚拟机统计信息监视工具 
- 1.3 jinfo: Java 配置信息工具 
- 1.4 jmap: Java 内存映像工具 
- 1.5 jhat: 虚拟机堆转储快照分析工具 
- 1.6 jstack: Java 堆栈跟踪工具 
- 1.7 基础工具总结 
 
- 2 可视化故障处理工具 - 2.1 JHSDB: 基于服务性代理的调试工具 
- 2.2 JConsole: Java 监视与管理控制台 
- 2.3 VisualVM: 多合-故障处理工具 
- 2.4 Java Mission Control: 可持续在线的监控工具 
 
- 3 HotSpot 虚拟机插件及工具 
JVM 性能调优
堆栈配置
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:MaxPermSize=16m -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxTenuringThreshold=0
-Xmx3550m: 最大堆大小为 3550m。
-Xms3550m: 设置初始堆大小为 3550m。
-Xmn2g: 设置年轻代大小为 2g。
-Xss128k: 每个线程的堆栈大小为 128k。垃圾收集器
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
-XX:+UseParallelGC-XX:ParallelGCThreads=20-XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5-XX:+UseCMSCompactAtFullCollection:
-XX:+UseParallelGC: 选择垃圾收集器为并行收集器。
-XX:ParallelGCThreads=20: 配置并行收集器的线程数
-XX:+UseConcMarkSweepGC: 设置年老代为并发收集。
-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次 GC 以后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection: 打开对年老代的压缩。可能会影响性能,但是可以消除碎片
辅助信息
-XX:+PrintGC-XX:+PrintGCDetails
-XX:+PrintGC 输出形式:
[GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]
-XX:+PrintGCDetails 输出形式:
[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs
高并发 JVM 调优
- 内存管理优化 - 固定堆大小: - -Xms与- -Xmx设为相同值(如- -Xms4g -Xmx4g)
- 年轻代占比 40%~50%: - -XX:NewRatio=2,Survivor 区比例- -XX:SurvivorRatio=8
- 限制元空间: - -XX:MaxMetaspaceSize=512m,大对象优先使用堆外内存(- DirectByteBuffer)
 
- 垃圾回收器选型 - G1:默认选择,平衡吞吐与延迟 - 参数: - -XX:+UseG1GC -XX:MaxGCPauseMillis=200
 
- ZGC:超低延迟(TB 级堆) - 参数: - -XX:+UseZGC -Xms16g -Xmx16g
 
- Shenandoah:高并发内存压缩 - 参数: - -XX:+UseShenandoahGC
 
 
- 线程与并发优化 - 线程池大小: - 线程数 = CPU 核心数 * (1 + 平均等待时间/计算时间)
- 队列类型:使用有界队列(如 - LinkedBlockingQueue)并设置拒绝策略
- 减少锁竞争:用 - ReentrantLock或- ThreadLocal替代- synchronized
 
- 监控与诊断工具 - 实时监控: - jstat -gcutil <pid> 1000、Arthas(- trace/- watch命令)
- 堆分析: - jmap -dump:format=b,file=heap.hprof <pid>+ MAT 工具
- GC 日志: - -Xlog:gc*:file=gc.log,用 GCViewer 分析停顿原因
 
- 核心避坑指南 - 避免动态堆扩容:固定 - -Xms=-Xmx防抖动
- 禁用 CMS:内存碎片问题,高并发场景不稳定 
- 警惕无界队列:导致 OOM,需设置合理队列容量 
 
JVM GC 响应优先与吞吐优先的区别是什么?
比如原来一个程序,10 秒收集一次,停顿时间 100ms,通过调整参数,变为 5 秒收集一次,停顿时间 70ms
很明显吞吐量下来了,但响应速度上去了
具体 JVM 猜应该是以老年代是并发还是并行为主的配置(待验证),可参考理解
JVM 垃圾回收器(串行、吞吐量优先、响应时间优先、G1open in new window
-XX:+UseParallelGC 与 -XX:+UseParNewGC 区别
- -XX:+UseParallelGC: - 选择垃圾收集器为并行收集器 
- 此配置仅对年轻代有效 
- 可以同时并行多个垃圾收集线程,但此时用户线程必须停止 
 
- -XX:+UseParNewGC - 设置年轻代为多线程收集 
- 可与 CMS 收集同时使用 
- 在 serial 基础上实现的多线程收集器 
 
场景题
常量存放的地方,在 JVM 的哪个部分
JVM 中常量根据类型存储在不同区域
- 字符串字面量在堆的字符串常量池 
- 类常量池在元空间 
- final 静态常量内联到代码 
- 基本字面量嵌入字节码指令 
Java 代码运行在哪边,线程运行在哪边
- 代码运行位置:JVM 进程 → 操作系统 → CPU 物理核心 
- 线程运行位置:OS 线程 → CPU 逻辑核心(物理核心或超线程) 
- 核心依赖:JVM 是代码和线程的运行时容器,但最终依赖底层 CPU 资源 
补充
- Java 代码(.java文件)编译为 字节码(.class文件),字节码运行在 JVM(Java 虚拟机) 中 
- JVM 本身是一个 本地进程(如 java.exe 或 javaw.exe),其进程由操作系统调度到 CPU 物理核心 上运行 
- Java 线程本质是操作系统原生线程的封装 - 在大多数现代 JVM 实现(如 HotSpot)中,java.lang.Thread 实例直接映射到 操作系统线程(如 Linux 的 pthread、Windows 的线程 API) 
- 线程调度由操作系统内核管理,具体运行在哪个 CPU 核心上由操作系统动态分配 
 
Java 对象的定位方式(对象怎么访问)
- Java 程序需要通过 JVM 栈上的引用访问堆中的具体对象 
- 对象的访问方式取决于 JVM 虚拟机的实现 
- 目前主流的访问方式有句柄和直接指针两种方式 - 指针: 指向对象,代表一个对象在内存中的起始地址 
- 句柄: 可以理解为指向指针的指针,维护着对象的指针 - 句柄不直接指向对象,而是指向对象的指针(句柄不发生变化,指向固定内存地址) 
- 再由对象的指针指向对象的真实内存地址 
 
- 直接指针: - 如果使用直接指针访问,引用中存储的直接就是对象地址 
- 那么 Java 堆对象内部的布局中就必须考虑如何放置访问类型数据的相关信息,其优点如下 - 速度更快,节省了一次指针定位的时间开销 
- 由于对象的访问在 Java 中非常频繁,因此这类开销积少成多后也是非常可观的执行成本 
- HotSpot 中采用的就是这种方式 
 
 
 

- 句柄访问 - Java 堆中划分出一块内存来作为句柄池,引用中存储对象的句柄地址 
- 而句柄中包含了对象实例数据与对象类型数据各自的具体地址信息,其优点如下 - 引用中存储的是稳定的句柄地址 
- 在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而引用本身不需要修改 
 
 
Java 中使用句柄的有如下场景
- Java 通过 FileInputStream、FileOutputStream 等类操作文件时,底层会使用操作系统的文件句柄(File Handle)。如果未正确关闭资源(如调用 close()),可能导致句柄泄漏 
- 在调用本地代码(C/C++)时,JNI 通过句柄(如 jobject、jclass)间接引用 Java 对象,避免直接暴露内存地址  
垃圾回收会回收 JVM 的哪些区域?
补充说明
- 直接内存(Direct Memory): - 不属于 JVM 运行时数据区,但被 JVM 间接管理(如 NIO 的 - ByteBuffer.allocateDirect)
- 回收依赖 - Cleaner机制(- PhantomReference),Full GC 时可能触发,但建议手动调用- System.gc()或显式释放(- ((DirectBuffer) buffer).cleaner().clean())
 
- 其他特殊区域: - Code Cache:JIT 编译后的本地代码,由 JVM 单独管理,不依赖 GC 
- 压缩类空间(Compressed Class Space):在启用压缩指针时存储类元数据,属于 Metaspace 的一部分 
 
- GC 不管理的区域: - 栈、程序计数器等线程私有区域,生命周期与线程绑定 
- 直接内存需结合手动释放或 - Cleaner机制
 
Full GC(Major GC)属于内存回收,它是 JVM 垃圾回收(Garbage Collection, GC)中最彻底的一种回收机制,会清理整个堆内存(包括新生代和老年代)以及方法区(Metaspace)的无效对象。
垃圾回收算法中的标记怎么实现、Java 垃圾回收算法使用的标记
垃圾回收算法中的标记阶段是实现内存管理的关键步骤,主要用于识别哪些对象是存活的,哪些是可以回收的
以下是几种常见的标记实现方式:
- 标记-清除算法中的标记 - 基本实现步骤: - 从根对象(全局变量、栈变量等)开始遍历 
- 对每个访问到的对象设置一个标记位(通常在对象头中) 
- 递归或迭代地标记所有可达对象 
 
 
- 三色标记法 - 更高效的标记方式,将对象分为三种颜色 - 白色:未访问(初始状态) 
- 灰色:已访问但引用的对象未完全处理 
- 黑色:已访问且引用的对象已完全处理 
 
- 实现过程 - 所有对象初始为白色 
- 根对象标记为灰色并入队 
- 从队列取出灰色对象,将其引用的白色对象标记为灰色 
- 处理完所有引用后,该对象标记为黑色 
- 重复直到灰色队列为空 
 
 
- 位图标记 - 对于大规模堆内存的高效标记 - 使用独立的位图来记录标记状态 
- 每个位对应堆中的一个对象或内存块 
- 减少对对象本身的修改,提高缓存效率 
 
 
- 写屏障与并发标记 - 现代 GC(如 G1、ZGC)使用写屏障技术支持并发标记 - 在对象引用修改时记录变化 
- 维护标记的一致性 
- 允许标记阶段与应用线程并发执行 
 
- 优化技术 - 并行标记:多线程同时进行标记 
- 增量标记:将标记过程分成小步骤执行,减少停顿 
- 卡表:记录堆中修改过的区域,缩小标记范围 
 
 
标记算法的选择取决于垃圾回收器的设计目标和应用程序的特性,注意以下一些说明:
- 卡表是位图的变种(粗粒度,记录内存区域而非单个对象) 
- ZGC 的染色指针本质上是一种"无位图"设计,将标记信息嵌入指针 
- Shenandoah 结合位图与 Brooks 指针实现并发移动 
垃圾回收机制整体介绍
- 基本工作原理 - 垃圾回收主要解决两个问题: - 识别垃圾对象: 找出不再被程序使用的对象 
- 回收内存空间: 释放这些对象占用的内存 
 
 
- 垃圾识别算法 - 引用计数法 - 每个对象维护一个引用计数器 
- 引用增加时计数器加 1, 减少时减 1 
- 计数器为 0 时立即回收 
- 缺点: 无法处理循环引用 
 
- 可达性分析 - 从 GC Roots(栈、 静态变量等) 出发 
- 遍历对象引用链 
- 不可达的对象即为垃圾 
- 主流 JVM 采用此方法 
 
- 垃圾回收算法 - 标记-清除(Mark-Sweep) - 标记阶段: 标记所有可达对象 
- 清除阶段: 回收未标记对象 
- 缺点: 产生内存碎片 
 
- 标记-整理(Mark-Compact) - 标记后, 将存活对象向一端移动 
- 解决碎片问题但增加开销 
 
- 复制算法(Copying) - 将内存分为两块 
- 只使用一块, 存活对象复制到另一块 
- 优点: 无碎片, 简单高效 
- 缺点: 内存利用率低 
 
- 分代收集(Generational) - 基于对象生命周期特点 
- 新生代(Young Generation): 频繁 GC, 使用复制算法 
- 老年代(Old Generation): 较少 GC, 使用标记-清除/整理 
 
 
- JVM 中的垃圾回收器 - Serial GC - 单线程, 简单高效 
- 适合客户端应用 
 
- Parallel GC - 多线程并行回收 
- 注重吞吐量 
 
- CMS(Concurrent Mark-Sweep) - 并发标记清除 
- 减少停顿时间 
- JDK9 后废弃 
 
- G1(Garbage-First) - 分区收集, 可预测停顿 
- JDK9+ 默认 
 
- ZGC - 亚毫秒级停顿 
- 支持 TB 级堆内存 
- JDK15+ 生产可用 
 
 
 
- 性能考量 - 衡量指标 - 吞吐量: GC 时间占总运行时间的比例 
- 停顿时间: 单次 GC 暂停应用的时间 
- 内存占用: GC 所需额外内存 
 
- 调优方向 - 合理设置堆大小 
- 选择适合场景的 GC 算法 
- 调整新生代/老年代比例 
- 监控 GC 日志分析瓶颈 
 
 
- 发展趋势 - 向低延迟方向发展(ZGC, Shenandoah) 
- 适应大内存和云原生环境 
- 更智能的自适应调优 
 
引用计数有什么缺点
- 循环引用导致内存泄漏 - 对象间相互引用时,引用计数无法归零,即使失去外部引用也无法释放 
 
- 性能开销 - 频繁的引用增减操作需更新计数器,高频率场景下可能成为性能瓶颈 
 
- 并发环境下的同步问题 - 多线程修改引用计数需依赖锁或原子操作,增加竞争和性能损耗 
 
- 内存释放的不确定性 - 存在循环引用时,内存释放需等待引用关系解除,可能延迟到程序结束 
 
- 无法处理复杂数据结构 - 树、图等复杂结构易产生循环引用,需额外设计(如弱引用)避免泄漏 
 
- 内存碎片化 - 按需回收对象可能导致内存空间不连续,影响大块内存分配效率 
 
- 对开发者透明性较低 - 需手动管理部分引用(如弱引用),否则易引入内存泄漏问题 
 
可达性分析从哪边开始
GC Roots 是可达性分析的起点,它们是程序运行中始终活跃的引用
Java 体系中,固定可作为 GC Roots 的对象包括以下几种
- 在虚拟机栈中(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等 
- 在方法区中类静态属性引用的对象,譬如 Java 类的引用类型静态变量。 
- 在方法区中常量引用的对象,譬如字符串常量池里的引用。 
- 在本地方法栈中 JNI(即 Native 方法) 引用的对象。 
- Java 虚拟机内部的引用,如基本数据类型对应的 Class 对象,一些常驻的异常对象(比如 NullPointException、OutOfMemoryError)等,还有系统类加载器。 
- 所有被同步锁(synchronized 关键字)持有的对象。 
- 反映 Java 虚拟机内部情况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等 
除了这些固定的 GC Roots 集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”地加入,共同构成完整“GC Roots”集合
三色回收算法
三色回收算法(Tri-color Marking)是一种高效的垃圾回收算法,核心思想是通过颜色标记跟踪对象存活状态,支持并发执行以减少停顿时间。以下是其核心内容整理:
核心思想
- 颜色定义 - 白色:未被访问的对象(可能为垃圾) 
- 灰色:已被访问但未完全扫描其引用的对象 
- 黑色:已被访问且所有引用均被扫描的对象(存活对象) 
 
- 目标 
 从GC Roots出发,遍历所有可达对象并标记为黑色,未被标记的白色对象视为可回收
算法流程
- 初始标记(Initial Marking) - 短暂 STW:标记所有直接从 GC Roots 可达的对象为灰色 
- 耗时短,仅需记录直接关联的根对象 
 
- 并发标记(Concurrent Marking) - 并发执行:从灰色对象出发,递归遍历其引用的对象 - 若对象未被标记,标记为灰色并加入队列 
- 已标记为灰色或黑色的对象跳过 
 
- 可能漏标:并发期间新创建的对象可能未被标记(需后续处理) 
 
- 重新标记(Remark) - 短暂 STW:修正并发标记期间因用户程序修改引用关系导致的漏标对象 
- 常用策略:增量更新(记录引用变化)或原始快照(SATB) 
 
- 并发清除(Concurrent Sweep) - 并发执行:回收所有白色对象(不可达对象),并整理内存 
 
三色定理与正确性
- 定理:当且仅当所有可达对象均为黑色,不可达对象为白色时,标记结果正确 
- 关键点:并发标记阶段需通过 SATB 或增量更新保证引用关系修改的记录,避免漏标 
优缺点
优点
- 支持并发:大部分工作与用户程序并发执行,减少停顿时间(仅初始标记和重新标记需短暂 STW) 
- 处理循环引用:通过可达性分析自动解决循环引用问题 
- 低误判率:结合 SATB 或增量更新保证标记正确性 
缺点
- 浮动垃圾:并发标记期间新创建的对象可能未被回收(需下一轮 GC 处理) 
- 内存占用:需维护对象颜色标记表,增加内存开销 
- 复杂度高:需处理并发冲突和状态同步问题 
应用场景
- CMS(Concurrent Mark-Sweep)收集器:通过三色标记实现低停顿的垃圾回收 
- G1(Garbage-First)收集器:在混合回收阶段使用三色标记划分 Region 优先级 
- 适用于多核、大内存场景:需平衡吞吐量和停顿时间 
与引用计数法对比
总结
三色标记法通过颜色划分和并发执行,在保证正确性的前提下显著降低GC停顿时间,是现代高性能垃圾回收器的核心算法
但其实现复杂度较高,需结合 SATB、增量更新等机制解决并发修改问题
CMS 垃圾回收器的实现机制
1. 核心目标
- 低停顿时间:专为减少老年代垃圾回收的 STW(Stop-The-World)时间设计,适用于对响应速度敏感的应用(如 Web 服务) 
2. 核心阶段
 CMS 基于标记-清除算法,分为四个阶段,其中两个阶段需 STW,两个阶段并发执行:
- 初始标记(Initial Mark) - STW:短暂暂停应用线程,仅标记直接关联 GC Roots 的对象(如栈中局部变量引用的对象) 
- 特点:耗时极短,通常与 Minor GC 配合触发 
 
- 并发标记(Concurrent Mark) - 并发执行:与应用线程并行遍历对象图,递归标记所有可达对象 
- 挑战:用户线程可能修改引用关系,导致漏标(需后续阶段修正) 
 
- 重新标记(Remark) - STW:修正并发标记阶段的漏标对象 
- 策略: - 增量更新(Incremental Update):记录引用变化,重新处理被修改的引用链 
- 原始快照(SATB, Snapshot-At-The-Beginning):基于并发标记开始时的对象快照,确保漏标对象被扫描 
 
 
- 并发清除(Concurrent Sweep) - 并发执行:回收不可达对象(白色对象),整理内存碎片 
- 特点:与应用线程并发,但可能因内存碎片导致后续分配失败 
 
3. 关键实现机制
- 三色标记法: - 白色:未被访问的对象(待回收) 
- 灰色:已访问但未完全扫描其引用的对象 
- 黑色:已访问且所有引用均被扫描的对象(存活) 
- 流程:从 GC Roots 出发,灰色对象逐步扩散至黑色,最终白色对象被回收 
 
- 并发与并行: - 并发:与用户线程同时运行(如并发标记、清除) 
- 并行:多线程协同完成某一阶段(如初始标记和重新标记) 
 
- 内存碎片处理: - CMS 不压缩内存,长期运行可能导致碎片化,触发 Full GC(使用 Serial Old 收集器) 
 
4. 缺点与局限性
- 浮动垃圾:并发标记期间产生的新垃圾无法立即回收,需等待下一轮 GC 
- 内存碎片:标记-清除算法导致碎片,可能引发 Full GC(STW 时间较长) 
- 并发模式失败:若并发阶段无法在限定时间内完成,会退化为Full GC 
- CPU资源竞争:并发阶段占用 CPU 资源,可能影响应用吞吐量 
5. 适用场景
- 对延迟敏感:需快速响应用户请求(如电商交易系统) 
- 中小内存堆:适合堆内存较小(如几十 GB)且碎片问题可控的场景 
- JDK 8 及之前版本:JDK 9+ 中已被 G1 替代,但部分旧项目仍需维护 
6. 对比其他收集器
7. 总结
- 优势:通过并发执行显著降低停顿时间,适合对延迟敏感的应用 
- 劣势:内存碎片、并发失败风险及 CPU 资源消耗 
- 面试要点: - 明确阶段划分及 STW 环节 
- 结合三色标记法解释漏标修正(SATB/增量更新) 
- 对比 G1 的适用场景差异(碎片处理、分区设计) 
 
详见 CMS 垃圾回收器
G1 垃圾回收器的实现机制
1. 核心目标
- 平衡吞吐量与延迟 - 在保证高吞吐量的同时,尽可能减少停顿时间(通常控制在几百毫秒内) 
- 适用于大内存、多核 CPU 且对延迟敏感的应用(如在线事务处理系统) 
 
2. 核心设计
- 分代与分区结合: - 分代:保留年轻代(Young Generation)和老年代(Old Generation)的概念 
- 分区 - 将整个堆划分为多个大小相等的 Region(默认 1~32MB,可通过 - -XX:G1HeapRegionSize调整)
- 每个 Region 独立管理,可以是 Eden、Survivor、Old 或 Humongous(存储超大对象) 
 
 
- Humongous 区域: - 存储超过 Region 50% 大小的对象,连续分配在多个 Region 中,回收时优先处理 
 
3. 垃圾回收过程
 G1 的回收分为 Young GC 和 Mixed GC,均包含 STW 和 并发 阶段:
- Young GC - 触发条件:Eden 区满时触发 
- 流程: - STW 初始标记:标记 GC Roots 直接关联的对象 
- 并发标记:遍历存活对象,记录引用关系 
- 复制存活对象:将存活对象从 Eden/Survivor 区复制到新的 Survivor 区或老年代 Region 
 
- 特点:年轻代 Region 回收后变为 Survivor 或老年代 Region 
 
- Mixed GC - 触发条件:老年代占用达到阈值(默认 45%)时触发 
- 流程: - 全局并发标记(与 Young GC 共享): - 初始标记(STW):标记直接关联对象 
- 根区域扫描:扫描 Survivor 区的引用 
- 并发标记:遍历全堆,标记存活对象 
- 最终标记(STW):处理并发阶段的引用变化(SATB 算法) 
- 筛选回收:根据 Region 的垃圾比例,选择回收价值高的 Region(优先回收年轻代 Region) 
 
- 并发清理:回收选定 Region 的垃圾,整理内存 
 
 
4. 关键技术点
- Remembered Set(RSet): - 每个 Region 维护一个 RSet,记录其他 Region 对该 Region 的引用(跨 Region 引用) 
- 作用:避免全堆扫描,提升回收效率。例如,仅扫描 RSet 中记录的引用链 
 
- SATB(Snapshot-At-The-Beginning)算法: - 在并发标记开始时生成对象快照,确保标记过程中新增的引用关系被记录(通过写屏障实现) 
- 用途:修正并发标记阶段的漏标对象 
 
- 混合回收策略: - 根据 Region 的垃圾占比(存活对象比例)动态选择回收目标,优先回收垃圾最多的 Region 
- 通过 - -XX:InitiatingHeapOccupancyPercent控制触发 Mixed GC 的老年代占用阈值
 
5. 优势与局限优势
- 可预测的停顿时间:通过筛选回收价值最高的 Region,控制单次 GC 停顿时间 
- 分代与分区结合:灵活管理不同生命周期的对象,减少碎片化 
- 高效处理大对象:Humongous 区域专门存储超大对象,避免内存浪费 
局限
- 内存占用:RSet 和 SATB 的写屏障增加内存和 CPU 开销 
- 内存碎片:虽通过复制算法减少碎片,但 Humongous 对象可能导致 Region 利用率低 
- 并发模式失败:若并发标记无法在停顿时间内完成,会触发 Full GC(使用 Serial Old 收集器) 
6. 参数调优
- 目标停顿时间: - -XX:MaxGCPauseMillis(默认 200ms,需根据业务调整)
- Region大小: - -XX:G1HeapRegionSize(建议根据堆大小选择,如 4GB 堆可设为 16MB)
- 并发线程数: - -XX:ConcGCThreads(控制并发标记阶段的线程数)
7. 与其他收集器对比
8. 面试高频问题
- G1 如何实现低停顿? - 通过分区回收、筛选高垃圾比例 Region,以及并发标记与混合回收结合 
 
- G1的 RSet 有什么作用? - 减少跨 Region 引用扫描,提升回收效率 
 
- G1 的 SATB 算法如何工作? - 在并发标记开始时生成对象快照,通过写屏障记录引用变化,确保标记准确性 
 
- G1 与 CMS 的适用场景差异? - G1 适合大堆且需平衡吞吐量与延迟的场景;CMS 适合中小堆且延迟敏感但对碎片不敏感的场景 
 
- G1 的 Full GC 触发条件? - 并发标记失败(如超时)、Humongous 对象分配失败、显式调用 - System.gc()
 
9. 总结
- 核心价值:G1 通过分区管理、并发标记和混合回收,在可控停顿时间内高效回收垃圾,是 JDK 9+ 的默认收集器 
- 面试重点:分代分区设计、SATB 与 RSet 机制、参数调优(如停顿时间与 Region 大小) 
- 避坑指南:避免过度调小 - MaxGCPauseMillis导致GC频繁,合理设置 Region 大小以适配应用对象分布
详见 G1 垃圾回收器
高级
JVM 的内存模型(运行时数据区)及每个模块的作用
- 栈 - 存放局部变量表、操作数栈、动态连接、方法出口等,线程私有 
- 局部变量表存放了: - 基本数据类型 
- 对象引用类型 
- 指向对象起始地址的引用指针 
- 指向一个对象的句柄 
- returnAddress 类型(指向了一条字节码指令的地址) 
 
 
- 本地方法栈 - 和虚拟栈相似,只不过它服务于 Native 方法,线程私有 
 
- 程序计数器 - 是一个较小的内存空间,当前线程所执行的行号指示器,用于记录正在执行的虚拟机字节指令地址,线程私有 
- 唯一一块 Java 虚拟机没有规定任何 OutofMemoryError 的区块 
 
- 堆 - Java 内存中最大的一块,所有对象实例、数组都存放在 Java 堆,GC 回收的地方,线程共享 
 
- 方法区 - 即非堆(元空间),存放已被加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据 
- 各线程共享 
- 回收目标主要是常量池的回收和类型的卸载 
 
详见 运行时数据区域
线上内存溢出分析
- top → 定位高内存进程 (PID) 
- top -Hp <PID> → 定位高内存线程 (TID) 
- printf "%x\n" <TID> → 转为十六进制 
- jstack <PID> | grep '0x4295' -C10 --color → 查看线程堆栈,关联业务代码 
- jmap -dump:format=b,file=my.dump <PID> → 生成 Heap Dump 
- MAT/VisualVM → 分析 Heap Dump 找到泄漏对象 
- 修复代码 → 优化内存使用 
- 监控验证 → 确保内存回归正常 
 
             
           
             
                         
             
            
评论区