"/>
侧边栏壁纸
博主头像
PySuper 博主等级

千里之行,始于足下

  • 累计撰写 286 篇文章
  • 累计创建 17 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

Java JVM-3

PySuper
2022-12-11 / 0 评论 / 0 点赞 / 6 阅读 / 0 字
温馨提示:
所有牛逼的人都有一段苦逼的岁月。 但是你只要像SB一样去坚持,终将牛逼!!! ✊✊✊

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 调优

  1. 内存管理优化

    • 固定堆大小:-Xms-Xmx 设为相同值(如 -Xms4g -Xmx4g

    • 年轻代占比 40%~50%:-XX:NewRatio=2,Survivor 区比例 -XX:SurvivorRatio=8

    • 限制元空间:-XX:MaxMetaspaceSize=512m,大对象优先使用堆外内存(DirectByteBuffer

  2. 垃圾回收器选型

    • G1:默认选择,平衡吞吐与延迟

      • 参数:-XX:+UseG1GC -XX:MaxGCPauseMillis=200

    • ZGC:超低延迟(TB 级堆)

      • 参数:-XX:+UseZGC -Xms16g -Xmx16g

    • Shenandoah:高并发内存压缩

      • 参数:-XX:+UseShenandoahGC

  3. 线程与并发优化

    • 线程池大小:线程数 = CPU 核心数 * (1 + 平均等待时间/计算时间)

    • 队列类型:使用有界队列(如 LinkedBlockingQueue)并设置拒绝策略

    • 减少锁竞争:用 ReentrantLockThreadLocal 替代 synchronized

  4. 监控与诊断工具

    • 实时监控:jstat -gcutil <pid> 1000、Arthas(trace/watch 命令)

    • 堆分析:jmap -dump:format=b,file=heap.hprof <pid> + MAT 工具

    • GC 日志:-Xlog:gc*:file=gc.log,用 GCViewer 分析停顿原因

  5. 核心避坑指南

    • 避免动态堆扩容:固定 -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 基础上实现的多线程收集器

特性

Parallel GC

ParNew GC

设计目标

高吞吐量

低延迟(需搭配 CMS)

老年代搭配回收器

Serial Old / Parallel Old(需显式启用)

CMS(强制依赖)

是否支持自适应调节

支持(-XX:+UseAdaptiveSizePolicy

不支持

JDK 默认版本

JDK8 及之前(新生代默认)

需显式启用(JDK9+ CMS 已废弃)

典型应用场景

离线计算、大数据处理

Web 服务(JDK8 及之前低延迟场景)

场景题

常量存放的地方,在 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 的哪些区域?

内存区域

是否由 GC 管理

存储内容

回收条件

堆(Heap)

✅ 是

对象实例、数组

对象无引用时回收(Minor GC/Full GC)

新生代(Young Gen)

✅ 是

新创建的对象

Eden 区满触发 Minor GC,存活对象复制到 Survivor 或晋升老年代

- Eden 区

✅ 是

新对象分配区

- Survivor 区(From/To)

✅ 是

经历 Minor GC 后存活的对象

老年代(Old Gen)

✅ 是

长期存活的对象

空间不足时触发 Full GC(标记-清除/标记-整理)

方法区(Metaspace)

✅ 是(部分)

类信息、常量池、静态变量

类卸载时回收(Full GC 触发,条件苛刻)

运行时常量池

✅ 是(部分)

字符串常量、符号引用

常量无引用时回收(Full GC 触发)

直接内存(Direct Memory)

❌ 否(需手动)

堆外内存(NIO Buffer 等)

依赖 UnsafeByteBuffercleaner 机制,或 Full GC 触发 Cleaner

虚拟机栈(VM Stack)

❌ 否

栈帧(局部变量、操作数栈)

方法执行结束自动弹出栈帧

本地方法栈(Native Stack)

❌ 否

Native 方法调用

线程结束时回收

程序计数器(PC Register)

❌ 否

当前线程执行的字节码行号

线程结束时回收

 补充说明

  1. 直接内存(Direct Memory)

    • 不属于 JVM 运行时数据区,但被 JVM 间接管理(如 NIO 的 ByteBuffer.allocateDirect

    • 回收依赖 Cleaner 机制(PhantomReference),Full GC 时可能触发,但建议手动调用 System.gc() 或显式释放(((DirectBuffer) buffer).cleaner().clean()

  2. 其他特殊区域

    • Code Cache:JIT 编译后的本地代码,由 JVM 单独管理,不依赖 GC

    • 压缩类空间(Compressed Class Space):在启用压缩指针时存储类元数据,属于 Metaspace 的一部分

  3. GC 不管理的区域

    • 栈、程序计数器等线程私有区域,生命周期与线程绑定

    • 直接内存需结合手动释放或 Cleaner 机制

Full GC(Major GC)属于内存回收,它是 JVM 垃圾回收(Garbage Collection, GC)中最彻底的一种回收机制,会清理整个堆内存(包括新生代和老年代)以及方法区(Metaspace)的无效对象。

垃圾回收算法中的标记怎么实现、Java 垃圾回收算法使用的标记

垃圾回收算法中的标记阶段是实现内存管理的关键步骤,主要用于识别哪些对象是存活的,哪些是可以回收的

以下是几种常见的标记实现方式:

  1. 标记-清除算法中的标记

    1. 基本实现步骤:

      1. 从根对象(全局变量、栈变量等)开始遍历

      2. 对每个访问到的对象设置一个标记位(通常在对象头中)

      3. 递归或迭代地标记所有可达对象

  2. 三色标记法

    1. 更高效的标记方式,将对象分为三种颜色

      1. 白色:未访问(初始状态)

      2. 灰色:已访问但引用的对象未完全处理

      3. 黑色:已访问且引用的对象已完全处理

    2. 实现过程

      1. 所有对象初始为白色

      2. 根对象标记为灰色并入队

      3. 从队列取出灰色对象,将其引用的白色对象标记为灰色

      4. 处理完所有引用后,该对象标记为黑色

      5. 重复直到灰色队列为空

  3. 位图标记

    1. 对于大规模堆内存的高效标记

      1. 使用独立的位图来记录标记状态

      2. 每个位对应堆中的一个对象或内存块

      3. 减少对对象本身的修改,提高缓存效率

  4. 写屏障与并发标记

    1. 现代 GC(如 G1、ZGC)使用写屏障技术支持并发标记

      1. 在对象引用修改时记录变化

      2. 维护标记的一致性

      3. 允许标记阶段与应用线程并发执行

    2. 优化技术

      1. 并行标记:多线程同时进行标记

      2. 增量标记:将标记过程分成小步骤执行,减少停顿

      3. 卡表:记录堆中修改过的区域,缩小标记范围

标记算法的选择取决于垃圾回收器的设计目标和应用程序的特性,注意以下一些说明:

  • 卡表是位图的变种(粗粒度,记录内存区域而非单个对象)

  • ZGC 的染色指针本质上是一种"无位图"设计,将标记信息嵌入指针

  • Shenandoah 结合位图与 Brooks 指针实现并发移动

垃圾回收算法

标记-清除算法中的标记

三色标记法

位图标记应用场景

写屏障与并发标记技术

Serial GC

递归标记对象头

未使用

卡表(记录跨代引用)

无(全程 STW)

Parallel GC

多线程并行标记

未使用

卡表优化年轻代回收

无(全程 STW)

CMS

并发标记(减少 STW)

是(老年代并发标记)

卡表 + 标记位图

写屏障维护卡表

G1 GC

分区标记(Region 级)

是(SATB 快照)

标记位图 + 记忆集

写屏障维护记忆集

Shenandoah

Brooks 指针辅助标记

是(并发标记)

位图标记存活对象

写屏障实现并发移动

ZGC

染色指针直接标记

是(指针元数据位标记)

无(染色指针替代位图)

读屏障+染色指针

垃圾回收机制整体介绍

  • 基本工作原理

    • 垃圾回收主要解决两个问题:

      • 识别垃圾对象: 找出不再被程序使用的对象

      • 回收内存空间: 释放这些对象占用的内存

  • 垃圾识别算法

    1. 引用计数法

      1. 每个对象维护一个引用计数器

      2. 引用增加时计数器加 1, 减少时减 1

      3. 计数器为 0 时立即回收

      4. 缺点: 无法处理循环引用

    2. 可达性分析

      1. 从 GC Roots(栈、 静态变量等) 出发

      2. 遍历对象引用链

      3. 不可达的对象即为垃圾

      4. 主流 JVM 采用此方法

    3. 垃圾回收算法

      1. 标记-清除(Mark-Sweep)

        • 标记阶段: 标记所有可达对象

        • 清除阶段: 回收未标记对象

        • 缺点: 产生内存碎片

      2. 标记-整理(Mark-Compact)

        • 标记后, 将存活对象向一端移动

        • 解决碎片问题但增加开销

      3. 复制算法(Copying)

        • 将内存分为两块

        • 只使用一块, 存活对象复制到另一块

        • 优点: 无碎片, 简单高效

        • 缺点: 内存利用率低

      4. 分代收集(Generational)

        • 基于对象生命周期特点

        • 新生代(Young Generation): 频繁 GC, 使用复制算法

        • 老年代(Old Generation): 较少 GC, 使用标记-清除/整理

    4. JVM 中的垃圾回收器

      1. Serial GC

        • 单线程, 简单高效

        • 适合客户端应用

      2. Parallel GC

        • 多线程并行回收

        • 注重吞吐量

      3. CMS(Concurrent Mark-Sweep)

        • 并发标记清除

        • 减少停顿时间

        • JDK9 后废弃

      4. G1(Garbage-First)

        • 分区收集, 可预测停顿

        • JDK9+ 默认

      5. ZGC

        • 亚毫秒级停顿

        • 支持 TB 级堆内存

        • JDK15+ 生产可用

  • 性能考量

    • 衡量指标

      • 吞吐量: GC 时间占总运行时间的比例

      • 停顿时间: 单次 GC 暂停应用的时间

      • 内存占用: GC 所需额外内存

    • 调优方向

      • 合理设置堆大小

      • 选择适合场景的 GC 算法

      • 调整新生代/老年代比例

      • 监控 GC 日志分析瓶颈

  • 发展趋势

    • 向低延迟方向发展(ZGC, Shenandoah)

    • 适应大内存和云原生环境

    • 更智能的自适应调优

引用计数有什么缺点

  1. 循环引用导致内存泄漏

    • 对象间相互引用时,引用计数无法归零,即使失去外部引用也无法释放

  2. 性能开销

    • 频繁的引用增减操作需更新计数器,高频率场景下可能成为性能瓶颈

  3. 并发环境下的同步问题

    • 多线程修改引用计数需依赖锁或原子操作,增加竞争和性能损耗

  4. 内存释放的不确定性

    • 存在循环引用时,内存释放需等待引用关系解除,可能延迟到程序结束

  5. 无法处理复杂数据结构

    • 树、图等复杂结构易产生循环引用,需额外设计(如弱引用)避免泄漏

  6. 内存碎片化

    • 按需回收对象可能导致内存空间不连续,影响大块内存分配效率

  7. 对开发者透明性较低

    • 需手动管理部分引用(如弱引用),否则易引入内存泄漏问题

可达性分析从哪边开始

GC Roots 是可达性分析的起点​​,它们是程序运行中始终活跃的引用

Java 体系中,固定可作为 GC Roots 的对象包括以下几种

  • 在虚拟机栈中(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等

  • 在方法区中类静态属性引用的对象,譬如 Java 类的引用类型静态变量。

  • 在方法区中常量引用的对象,譬如字符串常量池里的引用。

  • 在本地方法栈中 JNI(即 Native 方法) 引用的对象。

  • Java 虚拟机内部的引用,如基本数据类型对应的 Class 对象,一些常驻的异常对象(比如 NullPointException、OutOfMemoryError)等,还有系统类加载器。

  • 所有被同步锁(synchronized 关键字)持有的对象。

  • 反映 Java 虚拟机内部情况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等

除了这些固定的 GC Roots 集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”地加入,共同构成完整“GC Roots”集合

三色回收算法

 三色回收算法(Tri-color Marking)是一种高效的垃圾回收算法,核心思想是通过颜色标记跟踪对象存活状态,支持并发执行以减少停顿时间。以下是其核心内容整理:

核心思想

  1. 颜色定义

    • 白色:未被访问的对象(可能为垃圾)

    • 灰色:已被访问但未完全扫描其引用的对象

    • 黑色:已被访问且所有引用均被扫描的对象(存活对象)

  2. 目标
    从GC Roots出发,遍历所有可达对象并标记为黑色,未被标记的白色对象视为可回收

算法流程

  1. 初始标记(Initial Marking)

    • 短暂 STW:标记所有直接从 GC Roots 可达的对象为灰色

    • 耗时短,仅需记录直接关联的根对象

  2. 并发标记(Concurrent Marking)

    • 并发执行:从灰色对象出发,递归遍历其引用的对象

      • 若对象未被标记,标记为灰色并加入队列

      • 已标记为灰色或黑色的对象跳过

    • 可能漏标:并发期间新创建的对象可能未被标记(需后续处理)

  3. 重新标记(Remark)

    • 短暂 STW:修正并发标记期间因用户程序修改引用关系导致的漏标对象

    • 常用策略:增量更新(记录引用变化)或原始快照(SATB)

  4. 并发清除(Concurrent Sweep)

    • 并发执行:回收所有白色对象(不可达对象),并整理内存

三色定理与正确性

  • 定理:当且仅当所有可达对象均为黑色,不可达对象为白色时,标记结果正确

  • 关键点:并发标记阶段需通过 SATB 或增量更新保证引用关系修改的记录,避免漏标

优缺点

优点

  1. 支持并发:大部分工作与用户程序并发执行,减少停顿时间(仅初始标记和重新标记需短暂 STW)

  2. 处理循环引用:通过可达性分析自动解决循环引用问题

  3. 低误判率:结合 SATB 或增量更新保证标记正确性

缺点

  1. 浮动垃圾:并发标记期间新创建的对象可能未被回收(需下一轮 GC 处理)

  2. 内存占用:需维护对象颜色标记表,增加内存开销

  3. 复杂度高:需处理并发冲突和状态同步问题

应用场景

  • CMS(Concurrent Mark-Sweep)收集器:通过三色标记实现低停顿的垃圾回收

  • G1(Garbage-First)收集器:在混合回收阶段使用三色标记划分 Region 优先级

  • 适用于多核、大内存场景:需平衡吞吐量和停顿时间

与引用计数法对比

特性

三色标记法

引用计数法

循环引用

自动处理

无法处理

实时性

延迟回收(并发+标记阶段)

立即回收

性能开销

并发阶段低,但需 STW

每次引用操作均需更新计数器

适用场景

复杂引用关系、大内存堆

简单对象、实时性要求高

总结

三色标记法通过颜色划分和并发执行,在保证正确性的前提下显著降低GC停顿时间,是现代高性能垃圾回收器的核心算法

但其实现复杂度较高,需结合 SATB、增量更新等机制解决并发修改问题

CMS 垃圾回收器的实现机制

1. 核心目标

  • 低停顿时间:专为减少老年代垃圾回收的 STW(Stop-The-World)时间设计,适用于对响应速度敏感的应用(如 Web 服务)

2. 核心阶段
 CMS 基于标记-清除算法,分为四个阶段,其中两个阶段需 STW,两个阶段并发执行:

  1. 初始标记(Initial Mark)

    • STW:短暂暂停应用线程,仅标记直接关联 GC Roots 的对象(如栈中局部变量引用的对象)

    • 特点:耗时极短,通常与 Minor GC 配合触发

  2. 并发标记(Concurrent Mark)

    • 并发执行:与应用线程并行遍历对象图,递归标记所有可达对象

    • 挑战:用户线程可能修改引用关系,导致漏标(需后续阶段修正)

  3. 重新标记(Remark)

    • STW:修正并发标记阶段的漏标对象

    • 策略

      • 增量更新(Incremental Update):记录引用变化,重新处理被修改的引用链

      • 原始快照(SATB, Snapshot-At-The-Beginning):基于并发标记开始时的对象快照,确保漏标对象被扫描

  4. 并发清除(Concurrent Sweep)

    • 并发执行:回收不可达对象(白色对象),整理内存碎片

    • 特点:与应用线程并发,但可能因内存碎片导致后续分配失败

3. 关键实现机制

  • 三色标记法

    • 白色:未被访问的对象(待回收)

    • 灰色:已访问但未完全扫描其引用的对象

    • 黑色:已访问且所有引用均被扫描的对象(存活)

    • 流程:从 GC Roots 出发,灰色对象逐步扩散至黑色,最终白色对象被回收

  • 并发与并行

    • 并发:与用户线程同时运行(如并发标记、清除)

    • 并行:多线程协同完成某一阶段(如初始标记和重新标记)

  • 内存碎片处理

    • CMS 不压缩内存,长期运行可能导致碎片化,触发 Full GC(使用 Serial Old 收集器)

4. 缺点与局限性

  1. 浮动垃圾:并发标记期间产生的新垃圾无法立即回收,需等待下一轮 GC

  2. 内存碎片:标记-清除算法导致碎片,可能引发 Full GC(STW 时间较长)

  3. 并发模式失败:若并发阶段无法在限定时间内完成,会退化为Full GC

  4. CPU资源竞争:并发阶段占用 CPU 资源,可能影响应用吞吐量

5. 适用场景

  • 对延迟敏感:需快速响应用户请求(如电商交易系统)

  • 中小内存堆:适合堆内存较小(如几十 GB)且碎片问题可控的场景

  • JDK 8 及之前版本:JDK 9+ 中已被 G1 替代,但部分旧项目仍需维护

6. 对比其他收集器

特性

CMS

Parallel Old

G1

目标

低停顿

高吞吐

低停顿 + 高吞吐

算法

标记-清除

标记-整理

标记-整理(分 Region)

碎片问题

严重

通过 Compaction 缓解

适用堆大小

中小(<32GB)

大(多 Region 管理)

STW 时间

可控(优于 CMS)

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 GCMixed GC,均包含 STW并发 阶段:

  1. Young GC

    • 触发条件:Eden 区满时触发

    • 流程

      • STW 初始标记:标记 GC Roots 直接关联的对象

      • 并发标记:遍历存活对象,记录引用关系

      • 复制存活对象:将存活对象从 Eden/Survivor 区复制到新的 Survivor 区或老年代 Region

    • 特点:年轻代 Region 回收后变为 Survivor 或老年代 Region

  2. Mixed GC

    • 触发条件:老年代占用达到阈值(默认 45%)时触发

    • 流程

      • 全局并发标记(与 Young GC 共享):

        1. 初始标记(STW):标记直接关联对象

        2. 根区域扫描:扫描 Survivor 区的引用

        3. 并发标记:遍历全堆,标记存活对象

        4. 最终标记(STW):处理并发阶段的引用变化(SATB 算法)

        5. 筛选回收:根据 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. 优势与局限优势

  1. 可预测的停顿时间:通过筛选回收价值最高的 Region,控制单次 GC 停顿时间

  2. 分代与分区结合:灵活管理不同生命周期的对象,减少碎片化

  3. 高效处理大对象:Humongous 区域专门存储超大对象,避免内存浪费

局限

  1. 内存占用:RSet 和 SATB 的写屏障增加内存和 CPU 开销

  2. 内存碎片:虽通过复制算法减少碎片,但 Humongous 对象可能导致 Region 利用率低

  3. 并发模式失败:若并发标记无法在停顿时间内完成,会触发 Full GC(使用 Serial Old 收集器)

6. 参数调优

  • 目标停顿时间-XX:MaxGCPauseMillis(默认 200ms,需根据业务调整)

  • Region大小-XX:G1HeapRegionSize(建议根据堆大小选择,如 4GB 堆可设为 16MB)

  • 并发线程数-XX:ConcGCThreads(控制并发标记阶段的线程数)

7. 与其他收集器对比

特性

G1

CMS

ZGC/Shenandoah

算法

标记-整理

标记-清除

读屏障+染色指针

停顿时间

可控(毫秒级)

低(但浮动垃圾多)

极低(亚毫秒级)

内存碎片

严重

适用堆大小

中到大(几十GB)

中等(<32GB)

超大(TB 级)

适用场景

延迟敏感型应用

响应时间要求不高

超低延迟需求(如金融交易)

8. 面试高频问题

  1. G1 如何实现低停顿?

    • 通过分区回收、筛选高垃圾比例 Region,以及并发标记与混合回收结合

  2. G1的 RSet 有什么作用?

    • 减少跨 Region 引用扫描,提升回收效率

  3. G1 的 SATB 算法如何工作?

    • 在并发标记开始时生成对象快照,通过写屏障记录引用变化,确保标记准确性

  4. G1 与 CMS 的适用场景差异?

    • G1 适合大堆且需平衡吞吐量与延迟的场景;CMS 适合中小堆且延迟敏感但对碎片不敏感的场景

  5. 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 回收的地方,线程共享

  • 方法区

    • 即非堆(元空间),存放已被加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据

    • 各线程共享

    • 回收目标主要是常量池的回收和类型的卸载

详见 运行时数据区域

线上内存溢出分析

  1. top → 定位高内存进程 (PID)

  2. top -Hp <PID> → 定位高内存线程 (TID)

  3. printf "%x\n" <TID> → 转为十六进制

  4. jstack <PID> | grep '0x4295' -C10 --color → 查看线程堆栈,关联业务代码

  5. jmap -dump:format=b,file=my.dump <PID> → 生成 Heap Dump

  6. MAT/VisualVM → 分析 Heap Dump 找到泄漏对象

  7. 修复代码 → 优化内存使用

  8. 监控验证 → 确保内存回归正常

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区