深入理解JVM、高级特性、最佳实践(3)JVM的垃圾收集器

本文深入解析垃圾收集器的三大核心任务:确定哪些内存需要回收、何时回收及如何回收。介绍了引用计数算法与可达性分析算法,探讨了分代收集理论的三条经验法则,以及标记-清除、标记-复制和标记-整理三种垃圾收集算法的特点与应用场景。

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

垃圾收集器需要做的三件事情

  • 哪些内存需要回收?
  • 什么时候回收?
  • 如何回收?

哪些内存什么时候需要回收

对象是否已经宣告死亡?在Java堆中存放着几乎所有的对象实例,垃圾收集器在回收前的第一件事就是确定堆中哪些对象还“存活”着,哪些已经“死去”。

垃圾判定算法

引用计数算法

通过给每个对象添加一个引用计数器,每引用一次,计数器加一;当引用失效,计数器减一。当对象的计数器为0时说明对象不再被使用。引用计数器会增加对象占用的内存,还需要大量额外工作才能保证正确的工作,并且难以解决对象之间相互循环引用的问题。所以JVM没有选择引用计数器算法来管理内存,而是通过可达性分析确定对象是否存活的。

可达性分析算法

算法从GC Roots开始,向下搜索引用链上的可达对象。搜索结束后那些无法从GC Roots可达的对象会至少经历一次标记过程,第一次标记会判定是否需要执行finalize方法(有覆盖或者未被虚拟机调用finalize方法才需要执行),如果需要执行finalize,对象会被放入F-Queue中等待低优先级的Finalizer线程调用执行;稍后收集器会堆F-Queue中的对象进行第二次标记。在标记之后如果对象还没有在可达调用链上,将被认定为对象已经死亡,内存可以回收。为了保证可达性分析的正确性,需要将堆中对象的引用范围扩大到其它内存区域,因为堆中的对象完全有可能被位于其它区域的内存所引用。

GC Roots 一系列引用链上的初始节点的集合,通过这些节点集合可以搜索到所有的引用链。可以作为GC Roots对象包括:参数、局部变量、临时变量;静态属性引用的对象、常量引用的对象;JNI引用的对象;基本类型对应的Class对象、常驻的异常对象(NullPointException、OutOfMemoryError等)、系统类加载器;被同步锁持有的对象。

内存如何回收

垃圾收集算法

只分析和“可达性分析”算法相关的垃圾收集算法。

分代收集理论

三条经验法则

  1. 弱分代假说:绝大部分对象都是朝生夕灭的。
  2. 强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡。
  3. 跨代引用假说:跨代引用相对同代引用来说仅占极少数。
    对一个对象的一次尝试回收内存,标志着对象的年龄就增加了,随着对象经历的垃圾收集次数越多,对象就越难以消亡。因为随着年龄的增加,对象进入老年代,而垃圾收集器对老年代的内存收集频率较低,从而进入老年代的对消会存活的越久。

垃圾收集器将大部分的时间用来关注新生代的对象收集,能以较低代价回收到大量的空间;而那些难以消亡的老年代集中放在一块,JVM可以使用较低的频率来回收这个区域。这就同时兼顾了垃圾收集的时间开销和内存空间的有效利用。

收集行为
  1. 新生代收集(Minor GC/Young GC):值目标只是新生代的垃圾收集。
  2. 老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集。
  3. 混合收集(Mixed GC):收集整个新生代和部分老年代。
  4. 整堆收集(Full GC):收集整个Java堆和方法区。
标记-清除算法

算法分为“标记”和“清除”两个阶段:首先标记所有需要回收的对象,之后统一回收掉所有被标记的对象;也可以反过来,标记存活的对象,统一回收未被标记的对象。标记-清除算法是最基础的算法,基本都是对这个算法的优化改进。

缺陷一:执行效率不稳定,当Java堆中存在大量需要被回收的对象,就必须进行大量的标记和清除动作。
缺陷二:内存空间碎片化,标记、清除后会产生大量不连续的内存碎片。

标记-复制算法

该改进算法一定程度上解决了“标记-清除”算法的两个缺陷。算法将内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活的对象复制到另一块上面,然后把使用过的一块空间一次清理掉。对于多数对象都是可回收的情况,算法需要复制的对象就较少,也不用做多余的标记工作,从而执行效率一定程度上有提升。因为每次回收垃圾时,存活对象复制到另一个内存块中,使得对象在内存上可以实现连续,不会出现内存碎片。

引出一个缺陷:将可用内存缩小为原来的一半。根据经验法则一:“大多数对象熬不过第一次垃圾收集”,可以将内存划分进一步优化,从而解决问题,但依然存在或多或少的问题需要考虑。

标记-整理算法

标记-复制算法在存活对象较多的情况下需要执行较多的复制操作,效率将从而降低。所以“标记-复制”算法被应用在新生代对象的垃圾收集上,“标记-整理”算法针对的是老年代对象的收集。

标记过程与“标记-清除”算法一致,但后续不是对可回收对象进行清理,而是让所有存活的对象都向内存空间的一端移动,然后清理掉边界以外的内存。

由于对象的移动需要较长时间的停顿用户进程,造成一定的延迟,但不移动又会产生内存碎片增加内存分配的负担,所以不同的JVM针对不同的场景使用不同的算法。关注吞吐量则使用“标记-整理”算法;关注延迟则使用“标记-清理”算法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值