jvm_垃圾回收

本文详细介绍了JVM中的垃圾回收原理,包括不同类型的垃圾收集器如Serial、ParallelScavenge、CMS和G1的工作机制,以及各种算法如标记清除、复制、标记压缩。重点讨论了CMS和G1的特点,如CMS的低延迟和浮动垃圾问题,以及G1的空间整合能力。此外,还提到了内存管理中的参数调整和优化策略,如阈值设置和内存分配策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0.gc 实战

垃圾回收器学习之Full GC和CMS GC的区别

JVM实战:GC日志解析

GC日志分析 CMS FullGC时长

深入理解Major GC, Full GC, CMS cms gc 总结

JVM发生频繁 CMS GC,罪魁祸首是这个参数!

minor gc full gc 触发机制,如何减少full gc

1.概述

1.1 什么是垃圾

运行过程中没有任何指针指向的对象

2.垃圾算法

2.1 判别垃圾算法

2.1.1引用计数

有引用加1,没引用-1,为0回收。
弊端 存在循环引用,导致内存泄漏。 但是java 没有采用引用计数算法.
1

2.1.2 可达性分析算法 gc roots

通过gc roots 对象作为起点,从这个节点从上往下搜索,搜索走过的路径为引用链,当一个对象到gc roots 之间没有任何引用链时,证明这个对象不可达。可以被垃圾回收。
GC ROOTS 哪些

  1. 虚拟机栈,本地方法栈 引用的对象。
  2. 类的静态变量对象
  3. 方法区里面的常量, stringtable里的引用
  4. synchronized 对象

2.2 清除算法

注意 标记的是被引用的对象。

2.2.1 标记清除

cms 用
标记不是垃圾的对象,清除垃圾。
内存碎片化

标记:Collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。
清除:Collector对堆内存从头到尾进行线性的遍历,如果发现某个对象在其Header中没有标记为可达对象,则将其回收。

注意:何为清除?
这里所谓的清除并不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表里。下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够,就存放。

缺点:
1。 效率低,需要递归遍历2次全堆对象,一次是标记存活对象,另一次是找到不可导对象进行回收。
2. 会产生内存碎片。

a

2.2.2 标记压缩

  1. 标记存活对象
  2. 将所有存活对象压缩到内存一端,按顺序排放。
    标记-压缩算法 等价于 标记清除后,在进行一次压缩。

优点:
3. 消除了标记清除中的内存碎片问题。
4. 消除复制算法中,需要2倍的内存。
缺点
5. 效率上执行速度 比复制算法低。
6. 移动对象过程会 stw。
7.

1

2.2.3 复制算法

优点: 由空间换取时间,单次对象copy的成本要小于对象移动所需要的成本。
(2)标记复制算法不会出现 碎片问题
缺点:1. 需要两倍的空间。

a

2.2.4 分代收集

标记-压缩标记-清除复制
速度最慢一般最快
空间开销少 ,无内存碎片少,有内存碎片2配空间 无内存碎片
移动对象
分配对象方式指针碰撞空间列表指针碰撞
  • 年轻代(Young Gen)
    复制算法
  • 老年代
    标记-清除 cms垃圾收集器
    标记-压缩 其他垃圾收集器

cms 描述

以HotSpot中的CMS回收器为例,CMS是基于Mark-Sweep实现的,对于对象的回收效率很高。而对于碎片问题,CMS采用基于Mark-Compact算法的Serial Old回收器作为补偿措施:当内存回收不佳(碎片导致的Concurrent Mode Failure时),将采用Serial Old执行Full GC以达到对老年代内存的整理。

2.3 ststem.gc 和finalize()

System.gc() 是full gc
当一个对象 首次 考虑要被回收是,被回收之前 会执行 对象的finalize();
finalize 生命周期
大致描述一下finalize流程:当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。

3.内存泄漏情况

对象没有及时被回收,但又没有使用
内存泄漏的情况

  1. 静态类集合
public class MemoryLeak {
    static List list = new ArrayList();

    public void oomTests() {
        Object obj = new Object();//局部变量
        list.add(obj);
    }
}
  1. 单例模式
  2. 内部类持有外部类
  3. 各种io 连接资源
  4. 变量不合理的作用域
  5. 改变对象hash值,那么将永远存在set 集合中
public class ChangeHashCode {
    public static void main(String[] args) {
        HashSet set = new HashSet();
        Person p1 = new Person(1001, "AA");
        Person p2 = new Person(1002, "BB");

        set.add(p1);
        set.add(p2);
        p1.name = "CC";
        set.remove(p1);
        System.out.println(set);//2个对象!
        
//        set.add(new Person(1001, "CC"));
//        System.out.println(set);
//        set.add(new Person(1001, "AA"));
//        System.out.println(set);

    }
}

3.1 垃圾回收 并行并发,

并发意思: 一段时间内,单个 CPU 而言,它指的是 CPU 交替执行不同任务。
并行:多个cpu独自执行,不干扰

垃圾回收器并行

高吞吐量
多个垃圾回收线程 都在执行垃圾回收

垃圾回收并发 :

低延迟
垃圾回收线程 和用户线程 都在执行。

安全点 安全区域

stw
程序执行时并非在所有地方都能停顿下来开始GC,只有在特定的位置
才能停顿下来开始GC,这些位置称为“安全点(Safepoint)

Safe Point的选择很重要,如果太少可能导致GC等待的时间太长,
如果太频繁可能导致运行时的性能问题。大部分指令的执行时间都非
常短暂,通常会根据“是否具有让程序长时间执行的特征”为标准。
比如:选择些执行时间较长的指令作为Safe Point, 如方法调用、
循环跳转和异常跳转等。

如何在GC发生时,检查所有线程都跑到最近的安全点停顿下来呢?
主动式中断:
设置一个中断标志, 各个线程运行到Safe Point的时候主动轮询这个标志,如果中断标志为真, 则将自己进行中断挂起。

安全区域是指在一段代码片段中, 对象的引用关系不会发生变化, 在这个区域中的任何位置开始GC 都是安全的。

3.2 5种引用

4.cms 参数

4.3 threshold

-XX:MaxTenuringThreshold=threshold 新生代晋升老年代阈值
-XX:+PrintTenuringDistribution
-XX:+PrintTenuringDistribution参数作用:JVM 在每次新生代GC时,打印出幸存区中对象的年龄分布。

一般来说,垃圾回收器都会有默认的对象晋升到老年代的年龄参数阈值,CMS默认是6,G1默认是15,不过这参数值也不是一成不变的,它会根据动态年龄判断规则来重新被赋值。而使用-XX:+PrintTenuringDistribution参数就可以打印出对象的年龄分布,让开发者知道在自己的系统中对象晋升到老年代到底经过了几次GC,然后就可以根据年龄情况调整-XX:MaxTenuringThreshold参数或者调整其他参数让对象晋升到老年代的年龄阈值尽量达到开发者自己设置的理想阈值。

使用【-XX:+PrintTenuringDistribution】参数后,GC的信息如下:
1
以上信息表明,此次经过7次GC,新生代对象晋升到老年代,而最大允许的年龄阈值为15。

5. 垃圾回收 吞吐量和暂停时间

高吞吐量带来高延迟
低延迟带来低吞吐量

5.1 7款经典gc 和分代关系

aa

串行回收器:Serial. Serial Old
并行回收器:ParNew. Parallel Scavenge. Parallel Old
并发回收器:CMS. G1

----分代关系

新生代收集器: SerialParallel ScavengeParNeW;
老年代收集器: Serial 0ld、 Parallel 0ld、 CMS;
整堆收集器: G1

a
两个收集器间有连线,表明它们可以搭配使用:

Serial/Serial 01d、Serial/CMS、 ParNew/Serial 01d、ParNew/CMS、
Parallel Scavenge/Serial 01d、Parallel Scavenge/Parallel 0ld、G1;

其中Serial 0ld作为CMS 出现"Concurrent Mode Failure"失败的后 备预案
3.(红色虚线)由于维护和兼容性测试的成本,在JDK 8时将Serial+CMS、
ParNew+Serial 01d这两个组合声明为废弃(JEP 173) ,并在JDK 9中
完全取消了这些组合的支持(JEP214),即:移除。
(绿色虚线)JDK 14中:弃用Parallel Scavenge和Serial0ld GC组合(JEP366 )
(青色虚线)JDK 14中:删除CMS垃圾回收器 (JEP 363)

5.2 查看默认垃圾回收器

-XX:+PrintCommandLineFlags #查看命令行相关参数(包括垃圾回收器)

jinfo -flag 相关垃圾回收器参数 进程id

a

/**
 *  -XX:+PrintCommandLineFlags
 *
 *  -XX:+UseSerialGC:表明新生代使用Serial GC ,同时老年代使用Serial Old GC
 *
 *  -XX:+UseParNewGC:标明新生代使用ParNew GC
 *
 *  -XX:+UseParallelGC:表明新生代使用Parallel GC
 *  -XX:+UseParallelOldGC : 表明老年代使用 Parallel Old GC
 *  说明:二者可以相互激活
 *
 *  -XX:+UseConcMarkSweepGC:表明老年代使用CMS GC。同时,年轻代会触发对ParNew 的使用
 * -XX:ParallelGCThreads 限制线程数量,默认开启和CPU数据相
同的线程数。.
 */

5.3 Parallel Scavenge 8默认

# 虚拟机参数
# 并行
-XX:+UseParallelGC ~ -XX:+UseParallelOldGC
-XX:+UseAdaptiveSizePolicy
-XX:GCTimeRatio=ratio
-XX:MaxGCPauseMillis=ms
-XX:ParallelGCThreads=n

1

和ParNew收集器不同,Parallel Scavenge收集 器的目标则是达到
一个可控制的吞吐量(Throughput),它也被称为吞吐量优先的
垃圾收集器。
自适应调节策略也是Parallel Scavenge 与ParNew一个重要区别。

在程序吞吐量优先的应用场景中,Parallel 收集器
和Parallel 0ld收集器的组合,在Server模式下的内存
回收性能很不错。
在Java8中,默认是此垃圾收集器

-XX: +UseParallelGC

手动指定 年轻代使用Parallel并行收集器
执行内存回收任务。

-XX: +UseParallel0ldGc

手 动指定老年代都是使用并行回收
收集器。
分别适用于新生代和老年代。默认jdk8是开启的。
上面两个参数,默认开启一个,另一个也会被开启。(互相激活)

-XX: ParallelGCThreads

设置年轻代并行收集器的线程数。
一般地,最好与CPU数量相等,以避免过多的线程数影响垃圾收集性能。

在默认情况下,当CPU数量小于8个, Paralle lGCThreads 的值
等于CPU数量。
当CPU数量大于8个, ParallelGCThreads的值等于
3+[5*CPU_ Count]/8]

-XX :MaxGCPau3eMillis

设置垃圾收集器最大停顿时间(即STw的
时间)。单位是毫秒。

-XX:GCTimeRatio

参数的值则应当是一个大于0小于100的整数,也就是垃圾收集时间占总时间的比率,【这个参数设定为N的话,表示用户代码执行时间与总执行时间之比为N:N+1】

-XX: +UseAdaptiveSizePolicy

设 置Parallel Scavenge收集器具有自适应调节策略

在这种模式下,年轻代的大小、Eden和Survivor的比例、晋升老年
代的对象年龄等参数会被自动调整,已达到在堆大小、吞吐量和停
顿时间之间的平衡点。

5.4 cms

浮动垃圾 :本来可达,现在不可达

5.4.1 cms 4个步骤

2

  • 初始标记(stw): 所有进程运行到安全点暂停,只标记为gc roots 直接关联的对象,stw时间短
  • 并发标记:达到下一个安全点,从gc roots 直接关联对象,开始遍历整个对象图。这个过程时间长,但不会stw,不需要暂停用户线程。
  • 重新标记 stw:达到下一个安全点后,修正并发标记期间,用户线程导致的标记变动的记录。需要重新标记。此过程会STW,时间短。
  • 并发清除 (耗时操作):到下一个安全点后,清理掉标记阶段产生的死亡对象。
    浮动垃圾怎么处理

5.4.2 cms 为什么不采用标记压缩?

在清除阶段的时候,不能改变内存的地址,因为用户线程正在使用;所以只能对标记为垃圾的对象进行删除,而不能压缩内存碎片;如果强行需要整理,那就需要停止用户线程(STW)

5.4.3 cms 优缺点

优点:低延迟,并发收集。
缺点:

  • 会产生内存碎片,不会做内存压缩。分配大对象 触发full gc
  • cms无法处理浮动垃圾 。在并发标记阶段,用户线程和gc线程同时运行,产生新的垃圾对象,cms无法对这些垃圾对象进行标记,会导致新产生的垃圾没有被及时回收。而新的垃圾只有等到下次垃圾回收才能清理了。这些垃圾被称为浮动垃圾。 (并发标记阶段,不能保证新垃圾)

5.4.4 并发失败,serial-old

另外,由于在垃圾收集阶段用户线程没有中断,所以在CMS回收过程中,还应该确保应用程序用户线程有足够的内存可用。因此,CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收,以确保应用程序在CMS工作过程中依然有足够的空间支持应用程序运行。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用·Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。

5.4.5 参数

  • -XX:+UseConcMarkSweepGC
    • 手动指定使用CMS 收集器执行内存回收任务。
      开启该参数后会自动将-XX:+UseParNewGC打开。即:ParNew(Young区用)+CMS(Old区用)+Serial Old的组合。
  • -XX:CMSInitiatingOccupancyFraction=percent
    -XX:+UseCMSInitiatingOccupancyOnly 只是用设定的回收阈值(上面指定的70%),如果不指定,JVM仅在第一次使用设定值,后续则自动调整.
    配置的回收阈值-XX:CMSInitiatingOccupancyFraction=N会长期生效,否则只会第一次生效·
    • 如果你自己设置过-XX:CMSInitiatingOccupancyFraction=70,那么最终_initiating_occupancy=70%。在你设置了-XX:+UseCMSInitiatingOccupancyOnly的情况下,老年代内存使用率一旦超过70%就会执行CMS GC了。
    • 开始执行CMS垃圾回收时的内存占比,早期默认65,即只要老年代内存占用率达到65%的时候就要开始清理,留下35%的空间给新产生的浮动垃圾。
  • -XX:+UseCMSCompactAtFullCollection
    • 用于指定在执行完Full GC后对内存空间进行压缩整理,以此避免内存碎片的产生。不过由于内存压缩整理过程无法并发执行,所带来的问题就是停顿时间变得更长了。
  • -XX:CMSFullGCsBeforeCompaction
    • 设置在执行多少次Full GC后对内存空间进行压缩整理。
  • -XX:ParallelGCThreads=n
    • 表示并行的垃圾回收线程数,一般跟cpu数目相等
  • -XX:ConcGCTreads=threads
    • 并发的垃圾回收线程数目,一般是ParallelGCThreads的 1/4。即一个cpu做垃圾回收,剩下3个cpu留给人家用户线程。
  • -XX:+CMSScavengeBeforeRemark
    • 了解 CMS GC 的同学,一定知道 XX:CMSScavengeBeforeRemark 参数,它是用来开启或关闭在 CMS-remark 阶段之前的清除(Young GC)尝试。

    • 大家都知道CMS GC 只会回收 OldGen 的对象,那为什么需要这个参数? 由于 YoungGen 存在引用 OldGen 对象的情况,因此 CMS-remark 阶段会将 YoungGen 作为 OldGen 的 “GC ROOTS” 进行扫描,防止回收了不该回收的对象。而配置 -XX:+CMSScavengeBeforeRemark 参数,在 CMS GC 的 CMS-remark 阶段开始前先进行一次 Young GC,有利于减少 Young Gen 对 Old Gen 的无效引用,降低 CMS-remark 阶段的时间开销。

5.5 G1 针对大内存

g1笔记

G1 gc 在后台维护一个优先列表, 每次根据允许收集的时间,优先回收价值最大的Region

  1. 可以兼顾响应时间和吞吐量
  2. 划分多个region,以及humongous 区 存放大对象。

gc阶段
3. 新生代回收,eden 区不足,标记复制 stw
4. 并发标记 old 并发标记。 重新标记 会stw
5. 混合回收 包括 整个 新生代内存释放 和部分老年代的回收

5.5.0 g1特点

空间整合

  • CMS:“标记-清除”算法、内存碎片、若干次GC后进行一次碎片整理
  • G1将内存划分为一个个的region。内存的回收是以region作为基本单位的。Region之间是复制算法,但整体上实际可看作是标记-压缩(Mark-Compact)算法,两种算法都可以避免内存碎片。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次 GC。尤其是当Java堆非常大的时候,G1的优势更加明显。

5.5.1 参数设置

-XX:+UseG1GC 手动指定使用G1收集器执行内存回收任务。
-XX:G1HeapRegionSize 设置每个Region的大小。值是2的幂,
范围是1MB到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域。
默认是堆内存的1/2000。
-XX:MaxGCPauseMillis 设置期望达到的最大Gc停顿时间指标
(JVM会尽力实现,但不保证达到)。默认值是200ms
a
如图中的H块。主要用于存储大对象,如果超过1.5个region,
就放到H。

Remembered Set记忆集

通过Remembered Set记忆集来解决跨分区引用回收问题
一个区域分成若干个 卡 一个卡是512byte

22
3

写屏障

3

在eden 对象有个引用指向 old 的对象比如 eden 对象的 a.b=b(old)对象,执行完putfield指令后,会把a 对象的卡更新 在b 对象 的rset
准确的说 当更新发生的时候:

  1. 标记a 对象对应的卡为 dirty,
  2. 把卡 丢到 dirty queue 中 慢慢更新到rset,(防止多线程 竞争写rset 产生额外开销)
    队列有 (card 当前card 不多)
    绿 (激活一个 refine 优化线程 更新 rset)


    四种 颜色。

3

这样新生代在GC时,可以不用花大量的时间扫描所有年老代对象,来确定每一个对象的引用关系,而可以先扫描卡表,只有卡表的标记位为1时,才需要扫描给定区域的年老代对象。而卡表位为0的所在区域的年老代对象,一定不包含有对新生代的引用。

HotSpot JVM的卡页(Card Page)大小为512字节,卡表(Card Table)被实现为一个简单的字节数组,即卡表的每个标记项为1个字节

当对一个对象引用进行写操作时(对象引用改变),写屏障逻辑将会标记对象所在的卡页为dirty。
总结
Remember Set主要是为了解决跨代引用对GC的影响,Hot Spot实际使用的是一个叫做CardTable的结构。对于G1来说,每个Region都有一个CardTable,每个CardTable都有若干大小为512字节的cardpage用于存储其它Region的区域地址。当Region A的一个对象A在引用Region B的一个对象B的时候会通过写屏障将Region A区域的地址映射到Region B的CardTable中,标识对应card为dirty,之后在对Region B进行根节点枚举的时候,会把Region B的CardTable中被标记为dirty的card纳入到根节点的范围中进行扫描,以此避免去扫描Region A。

卡表的理解:

g1垃圾回收过程

1

年轻代gc stw

年轻代垃圾回收只会回收Eden区和Survivor区。
会stw

  1. 扫描根对象,static指向的对象,方法上的局部变量,reset 上的外部引用对象,避免全堆扫描。
  2. 处理脏卡队列 (排空 队列的card ,把rset 更新成最新的状态。),更新rset,反应rset真实的引用关系。·此阶段完成后,RSet可以准确的反映老年代对所在的内存分段中对象的引用。·
  3. 处理rset,(因此 rset只是记录了某张卡里存在了某个对象引用,具体哪个对象不知道。) 扫描一下 rset 中 卡 对应的堆内存空间 找到真正的引用对象 识别被老年代指向eden对象,这些被指向的对象(eden)是存活的
    4. 复制对象。eden–>survivor 复制算法
    5. 处理引用。强软弱虚
老年代并发标记过程

随着时间推移,越来越多的对象晋升到老年代中,当老年代占比(相对于Java总堆而言)达到IHOP参数(上图的IHOP Trigger)之后,那么G1首先会触发并发标记周期(上图的Concurrent Marking Cycle),当完成后才会开始下一小节的混合垃圾收集周期

  1. 初始标记 stw 只标识 根节点直接可达的。(piggy-backed on ygc 利用年轻代信息)触发ygc
  2. 根区域扫描 扫描survivor区直接可达的老年代区域对象。并标记为被引用对象 在yc 前执行。
  3. ·并发标记 和用户线程一起执行,在整个堆中进行并发标记。 如果发现整个region 对象都是垃圾,直接回收。
  4. 再次标记 ,用于上次并发标记的标记引用修正 stw (采用STAB 算法 初始快照算法)
  5. 独占清理 clean up 不进行垃圾回收。
    计算各个区域的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域。为下阶段做铺垫。是STW的。
    这个阶段并不会实际上去做垃圾的收集
  6. 并发清理
mixed gc

当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC,该算法并不是一个Old GC,除了回收整个Young Region,还会回收一部分的Old Region。这里需要注意:·是一部分老年代,而不是全部老年代。

full gc

G1的初衷就是要避免Full GC的出现。但是如果上述方式不能正常工作,·G1会停止应用程序的执行(Stop-The-World),使用单线程的内存回收算法进行垃圾回收,性能会非常差,应用程序停顿时间会很长。
1

InitiatingHeapOccupancyPercent

G1里的XX:InitiatingHeapOccupancyPercent,默认是45。他看网上有两种说法,一种是整个堆占用率超过45%时开始并发标记周期;另一种说是old region占用超过45%时开始并发标记周期;

如果你使用的JDK版本在8b12之前,那么文章开头的第一种说法是正确的,即XX:InitiatingHeapOccupancyPercent是整个堆使用量与堆总体容量的比值;
如果你使用的JDK版本在8b12之后(包括大版本9、10、11…),那么文章开头第二种说法是正确的,即XX:InitiatingHeapOccupancyPercent是·老年代大小堆总体容量的比值

CSet(Collection Set)

一组可被回收的分区Region的集合;Region,是多个对象的集合内存区域。

三色标记法

三色标记算法是什么?
黑色 当前对象检查过,和当前对象的引用对象也检查过了。
白色 没有被标记的对象
灰色 自身被标记,成员变量未被标记。
3

并发标记 漏标

多标,不需要解决,没有被回收造成浮动垃圾。等下次gc 回收即可。
同时满足 才会发生漏标

  1. 一个或者多个黑色对象,有了对白色对象的新引用
  2. 删除了全部灰色对象到 白色对象的引用,

b->d 消失同时 a->d 增加
3

cms 解决方案:增量更新
打破第一个规律
通过写屏障,将A新增的成员变量D记录下来。(A黑色对象在自己标记后,又重新指向了白色对象)。将A对象记录下来, 在重新标记阶段,再以这个黑色对象为根,对其引用重新扫描。这样白色对象d会变成灰色对象。
打破第二个规律 stab 原始快照
在灰色对象删除白色对象引用前,先将灰色对象引用白色对象记录下来。
在并发标记结束后,按照记录下来的对象图,对灰色对象的引用进行扫描,避免了漏标。 这种方式可能会产生浮动垃圾。

针对漏标问题
如何保证收集线程与用户线程互不干扰的运行? (在垃圾回收过程中,原有的对象结构被打破了)
cms 增量更新
g1 原始快照 STAB

增量更新解决方案就是
我们可以利用写屏障,将D新的成员变量引用对象G记录下来
既然有黑色对象在自己标记后,又重新指向了白色对象。那么我就把这个黑色对象的引用记录下来,在后续「重新标记」阶段再以这个黑色对象为跟,对其引用进行重新扫描。通过这种方式,被黑色对象引用的白色对象就会变成灰色,从而变为存活状态。

·原始快照·则是去破坏第二个条件,·当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来并发扫描结束后,在将这些记录重新扫描一次。
随后在「重新标记」阶段再以灰色对象为根,对它的引用进行扫描,从而避免了漏标的问题。通过这种方式,原本漏标的对象就会被重新扫描变成灰色,从而变为存活状态。
这种方式有个缺点,就是会产生浮动垃圾。 因为当用户线程取消引用的时候,有可能是真的取消引用,对应的对象是真的要回收掉的。这时候我们通过这种方式,就会把本该回收的对象又复活了,从而导致出现浮动垃圾。但相对于本该存活的对象被回收,这个代码还是可以接受的,毕竟在下次 GC 的时候就可以回收了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值