四种GC算法
1. 引入计数算法
1.1 算法思想
每个对象在创建的时候,就给这个对象绑定一个计数器。每当有一个引用指向该对象时,计数器加一;每当一个指向它的引用被删除时,计数器减一。这样,当没有引用指向该对象时,该对象死亡,计数器为0;这时就应该对这个对象进行垃圾回收操作(一般不是一个对象被引用的次数为0了就立即释放,出于效率考虑,系统总是会等一批对象一起处理,这样更加高效)
1.2 核心思想
为每个对象额外存储一个计数器RC
,根据RC
的值来判断对象是否死亡,从而判断是否执行GC
操作
1.3 优点
- 简单
- 计算代价分散
- “幽灵时间”短(幽灵时间是指对象死亡到回收的这段时间,处于幽灵状态)
1.4 缺点
- 不全面(容易漏掉循环引用的对象)
- 并发支持较弱
- 占用额外内存空间
- 循环依赖的对象没办法回收(A对象引用B对象,B对象引用A对象,计数器始终不为零)
2. 标记-清除算法
2.1 算法思想
为每个对象存储一个标志位
,记录对象的状态(存活或者死亡)。分为两个阶段,一个是标记阶段,这个阶段内,为每个对象更新标志位,检查对象是否死亡;第二个阶段是清除阶段,该阶段对死亡的对象进行清除,执行GC
操作。
标记清除算法最重要的优点,就是解决了引用计数的循环引用而导致内存泄露的问题,因为只标记可达的对象,如果一系列对象形成了环,但这个环上的所有对象都是不可达的,那么在引用跟踪的活着对象集合里就不包括环,环上的对象都属于不可达对象,都会被清除。
2.2 优点
- 最大的优点是,相比于引用计数法,标记-清除算法中每个活着的对象的引用只需要找到一个即可,
找到一个就可以判断它为活的
。 - 此外,这个算法相比于引用计数法更全面,在指针操作上也没有太多的花销。更重要的是,这个算法并不移动对象的位置(后面俩算法涉及到移动位置的问题)。
2.3 缺点
- 很长的幽灵时间,判断对象已经死亡,消耗了很多时间,这样对象死亡到对象被回收之间的时间过长。
- 每个活着的对象都要在标记阶段遍历一遍;所有对象都要在清除阶段扫描一遍,因此算法复杂度较高。
- 没有移动对象,导致可能出现很多碎片空间无法利用的情况。
3. 标记-整理算法
3.1 算法思想
标记-整理法是标记-清除法的一个改进版。同样,在标记阶段,该算法也将所有对象标记为存活和死亡两种状态;不同的是,在第二个阶段,该算法并没有直接对死亡的对象进行清理,而是将所有存活的对象整理一下,放到另一处空间,然后把剩下的所有对象全部清除掉。这样就达到了标记-整理的目的。
3.2 优点
- 该算法不会标记-清除算法那样产生大量的碎片空间
3.3 缺点
- 如果存活的对象过多,整理阶段将会执行较多复制操作,导致算法效率降低。
3.4 案例
上面是标记阶段,下面是整理之后的状态。可以看到,该算法不会产生大量碎片内存空间。
4. 复制算法
4.1 算法思想
该算法将内存平均分为两部分,然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去。
注意:
这个算法与标记-整理算法的区别在于,该算法不是同一个区域复制,而是将存活的对象复制到另一个区域内。
4.2 优点
- 实现简单
- 不产生内存碎片
4.3 缺点
- 每次运行,总有一半内存是空的,导致可使用的内存空间只有原来的一半。
5. 总结
- 引用计数法不常用,其他三种算法在jvm上是很常见的
- JVM 的年轻代使用的是复制算法来进行垃圾回收(由于其中的存活对象比较小)
- 老年代使用的却是标记-清除算法或标记-整理算法(由于每次都只回收少量对象)
年轻代和老年代的概念:
-
新生代(Yong Gen)
年轻代特点:区域相对老年代较小,对象生存周期短,存活率低,回收频繁。所以适合-标记-复制算法;
-
老年代(Tenured Gen)
老年代特点:区域较大,对象生命周期长、存活率高,回收不频繁,所以更适合-标记-整理算法;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构