如何定位垃圾
- 引用计数 Reference Counting
缺点:无法解决循环引用 - 可达性分析 Tracing GC(GC Root)
可以作为 GC Root 的对象
- JVM stack:Java 虚拟机栈(栈帧中的本地变量表)引用的对象
- native method stack:本地方法栈中引用的对象
- run-time constant:运行时常量池
- static references in method area:方法区中静态变量引用的对象
常见 GC 算法
- 标记清除 Mark-Sweep(老年代)
缺点:内存不连续,产生碎片 - 拷贝算法 Copying (年轻代,98%朝生夕死)
优点:内存连续,没有碎片 缺点:空间开销高 - 标记整理 Mark-Compact(老年代)
优点:没有碎片,不浪费空间 缺点:效率偏低
算法 | 移动对象 | 空间开销 | 时间开销 |
---|
Mark-Sweep | 否 | 低(有碎片) | mark 阶段与存活对象的数量成正比 O(L),sweep 阶段与整堆大小成正比 O(H) | Mark-Compact | 是 | 低(无碎片) | mark 阶段与存活对象的数量成正比 O(L),compaction 阶段与存活对象的大小成正比 O(L) | Copying | 是 | 高 | 与存活对象大小成正比 O(L) |
堆内存分代模型
新生代 + 老年代 + 永久代(1.7) / 元数据区(1.8) Meta Space 新生代 = eden + 2 * survivor
元数据区大小受限于物料内存 1.7 字符串常量存在永久代,1.8 存在堆
Eden : Survivor From : Survivor To = 8 : 1 : 1 新生代 : 老年代 = 1 : 2
年轻代回收过程
- YGC(Young GC)回收后,eden 大多数对象会被回收,存活的对象拷贝到 survivor 0
- 再次 YGC,eden + survivor 0 存活的对象拷贝到 survivor 1
- 再次 YGC,eden + survivor 1 拷贝到 survivor 0
- 每被复制一次,年龄 + 1,年龄超过 -XX:MaxTenuringThreshold 指定次数,进入老年代
(Parallel Scavenge:15,CMS:6,G1:15) - survivor 装不下,进入老年代
对象什么时候进入老年代?
对象头 mark word 有4位是记录对象年龄的,4位二进制最大是15,也就是说年龄最大为15
老年代回收
老年代满了进行FGC(Full GC),GC调优主要目的:尽量减少FGC
Minor GC = Young GC Major GC = Full GC
java -XX:+PrintFlagsFinal:查看最终参数值 java -XX:+PrintFlagsInitial:查看默认参数值 java -XX:+PrintCommandLineFlags:查看命令行参数
垃圾回收器
- 1.8 默认垃圾回收器:Parallel Scavenge + Parallel Old
Serial 应用在年轻代 串行回收
ParNew 并行回收 配合CMS的并行回收 年轻代
SerialOld
Parallel Scavenge 应用在年轻代 并行回收 重视吞吐量
吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
ParallelOld
CMS 老年代 并行
垃圾回收和应用程序同时运行,降低STW时间(降低到200ms)
四个过程
- 初始标记:标记 GC Root 能直接关联的对象,会stw但时间短
- 并发标记:
- 重新标记:修正并发标记期间用户程序运行而变化的标记记录,会 STW 但时间较短
- 并发清除
三个缺点
- 占用 CPU 资源,默认开启垃圾回收线程数 = (CPU数 + 3) / 4,4核以上 CPU 会占用不少于 25% 的 CPU 资源
- 无法清理浮动垃圾(并发清理阶段用户程序新产生的垃圾,CMS 无法标记,只能留到下一次 CG 再清理)
- 由于清理时用户程序还在运行,需要留一部分空间供并发收集时的程序运行使用,无法像其他回收器那样等到老年代快填满再触发收集,1.5 默认老年代 68% 触发收集,1.6 默认 92%,预留空间不足会出现 Concurrent Mode Failure 失败,性能降低
- 解决方案:降低触发 CMS 的阈值,–XX:CMSInitiatingOccupancyFraction 92% 可以降低这个值,让 CMS 保持老年代足够的空间
- 基于标记-清除算法,产生空间碎片,-XX:+UseCMSCompactAtFullCollection 可以设置快要 FGC 前进行碎片整理,但停顿时间边长 -XX:CMSFullGCsBeforeCompaction 默认为0,指的是经过多少次 FGC 才进行压缩,当碎片化内存装不下其他对象时,会使用 Serial Old 进行标记整理
G1(10ms)不再进行物理分代,采用逻辑分代
- 将内存分为一个个的 Region。一块 Region(分区)在逻辑上依然分代,分为四种:Eden,Old,Survivor,Humongous(大对象,跨多个连续的 Region)。
- 它的每个分区都可能是年轻代也可能是老年代,但是在同一时刻只能属于某个代。
- 可预测的停顿(分割堆成大小相等的多个 Region,追踪 Region 中垃圾堆积价值大小,维护优先级名单)
- 首先回收垃圾最多的分区,花费较少的时间来回收这些分区的垃圾,这也就是 G1 名字的由来
- 在回收老年代的分区时,是将存活的对象从一个分区拷贝到另一个可用分区,这个拷贝的过程就实现了局部的压缩。每个分区的大小从 1M 到 32M 不等,但都是2的幂次方。
MixedGC -XX:InitiatingHeapOccupacyPercent,默认值45%,当堆内存超过这个值,触发MixedGC 回收时不分新生代还是老年代什么的,region满了就回收。
四个步骤
- 初始标记 STW
- 并发标记
- 最终标记 STW(重新标记)
- 筛选回收 STW(并行)
- 跟 CMS 非常像,MixedGC 最后是筛选回收,多了个筛选步骤。筛选就是找出垃圾最多的 region。筛选后将存活对象复制到其他 region,再将之前的 region 清空。
CMS 与 G1 比较
三色标记算法
- 白色:该对象没有被标记过。(对象垃圾)
- 灰色:该对象已经被标记过了,但该对象下的属性没有全被标记完。(GC需要从此对象中去寻找垃圾)
- 黑色:该对象已经被标记过了,且该对象下的属性也全部都被标记过了。(程序所需要的对象)
三色标记存在问题
- 浮动垃圾:并发标记的过程中,若一个已经被标记成黑色或者灰色的对象,突然变成了垃圾,由于不会再对黑色标记过的对象重新扫描,所以不会被发现,那么这个对象不是白色的但是不会被清除,重新标记也不能从 GC Root 中去找到,所以成为了浮动垃圾,浮动垃圾对系统的影响不大,留给下一次GC进行处理即可。
- 对象漏标问题(需要的对象被回收):并发标记的过程中,一个业务线程将一个未被扫描过的白色对象断开引用成为垃圾(删除引用),同时黑色对象引用了该对象(增加引用)(这两部可以不分先后顺序);因为黑色对象的含义为其属性都已经被标记过了,重新标记也不会从黑色对象中去找,导致该对象被程序所需要,却又要被GC回收。
当且仅当一下两个条件同时满足时,会产生对象消失的问题
- 赋值器删除了全部从灰色对象到白色对象的直接或间接引用
- 赋值器插入了一条或多条从黑色对象到白色对象的新引用
所以要解决漏标问题,打破两个条件之一即可:
-
CMS:增量更新 Incremental Update,跟踪黑指向白的增加,破坏条件2 当黑色对象插入新的指向白色对象的引用关系时,将这个新插入的引用记录下来,等到并发扫描结束后,再将记录过引用关系中的黑色对象为根,重新扫描。 简化理解为:黑色对象一旦插入指向白色对象的引用,它就变回灰色对象了 -
G1:原始快照 Snapshot At The Beginning SATB,记录灰指向白的消失,破坏条件1 当灰色对象要删除指向白色对象的引用关系时,就把这个要删除的引用记录下来,并发扫描结束后,再将这些记录过引用关系的灰色对象为根,重新扫描一遍。 简化理解为:当灰->白消失时,要把这个 引用 推到 GC 的堆栈,保证白还能被 GC 扫描到
为什么G1采用SATB而不用incremental update?
因为采用incremental update把黑色重新标记为灰色后,之前扫描过的还要再扫描一遍,效率太低。
Incremental Update 并发标记漏标的问题
CMS通过重新扫描一遍解决了上述问题
|