Java垃圾收集器和go当中的区别


垃圾回收算法主要分为三种:标记-清除,标记-复制和标记-整理。
其中,Java语言是基于分代的,在新生代主要使用了标记-复制,在新生代中分为eden,s0区和s1区。
其中,绝大多数对象都是直接在eden区进行分配,然后s0区和s1区空闲的区域进行复制工作。而老年代主要使用标记-整理算法,将标记后的对象统一移动到内存区域的前部分,然后对后面的区域进行清理。
而go当中主要使用了标记-清除算法,其主要原理是对对象进行标记,然后回收未被标记过的对象。具体的算法就不在此详述,本文想要探索的是java和go当中垃圾回收的不同点。

要想找到不同点,首先要找到相同点。即标记,三种算法均是基于标记算法产生的,不同地方在于对于标记后的行为的处理,但这其实并不是关键,因为不管是复制、整理还是清除,均是基于内存区域特点或语言特点决定的,如新生代常常大批量死亡,所以只需要复制存活对象,这样效率高,而老年代主要是大对象以及存活时间较长的对象,变化不大,因此进行整理,而go语言追求简单,因此使用清除。

因为有许多文章对这三种算法进行比较,因此本文就不再赘述,而本文要说的是Java和go当中标记的不同点。

标记过程中的主要问题:
1.断开引用
假设已经遍历到E(变为灰色了),此时应用执行了 objD.fieldE = null :
在这里插入图片描述
此刻之后,对象E/F/G是“应该”被回收的。然而因为E已经变为灰色了,其仍会被当作存活对象继续遍历下去。最终的结果是:这部分对象仍会被标记为存活,即本轮GC不会回收这部分内存。

这部分本应该回收 但是 没有回收到的内存,被称之为“浮动垃圾”。浮动垃圾并不会影响应用程序的正确性,只是需要等到下一轮垃圾回收中才被清除。
这种还好,还是可以清除的,但下面这种:
2.插入引用
在这里插入图片描述
此时切回GC线程继续跑,因为E已经没有对G的引用了,所以不会将G放到灰色集合;尽管因为D重新引用了G,但因为D已经是黑色了,不会再重新做遍历处理。
最终导致的结果是:G会一直停留在白色集合中,最后被当作垃圾进行清除。这直接影响到了应用程序的正确性,是不可接受的。

所以,垃圾回收器均采用了一些手段:
Java:

CMS:写屏障+增量更新

当对象D的成员变量的引用发生变化时(objD.fieldG = G;),我们可以利用写屏障,将D新的成员变量引用对象G记录下来:
【当有新引用插入进来时,记录下新的引用对象】
这种做法的思路是:不要求保留原始快照,而是针对新增的引用,将其记录下来等待遍历,即增量更新(Incremental Update)。

G1:写屏障+SATB

当对象E的成员变量的引用发生变化时(objE.fieldG = null;),我们可以利用写屏障,将E原来成员变量的引用对象G记录下来:
【当原来成员变量的引用发生变化之前,记录下原来的引用对象】
这种做法的思路是:尝试保留开始时的对象图,即原始快照(Snapshot At The Beginning,SATB),当某个时刻 的GC Roots确定后,当时的对象图就已经确定了。
比如 当时 D是引用着G的,那后续的标记也应该是按照这个时刻的对象图走(D引用着G)。如果期间发生变化,则可以记录起来,保证标记依然按照原本的视图来。
值得一提的是,扫描所有GC Roots 这个操作(即初始标记)通常是需要STW的,否则有可能永远都扫不完,因为并发期间可能增加新的GC Roots。

ZGC:读屏障

go:混合屏障

其实就是上面两个的混合

写着写着发现,没啥区别,基本就是Java中一个删除写屏障一个插入写屏障,然后go将其混合使用。

但还有一个问题,既然删除写屏障和插入写屏障都可以解决该问题,为什么go要将其混合呢?

三种方法的特点:

标记清除:
1.执行效率不稳定,如果堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低
2.内存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片
标记复制:
1.需要额外的空间留给复制对象,浪费内存空间
2.如果存活对象多,则复制开销大
标记整理:
如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活区域,移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作必须全程暂停用户应用程序才能进行,这就更加让使用者不得不小心翼翼地权衡其弊端了。

但如果跟标记清除算法那样完全不考虑移动和整理存活对象的话,弥散于堆中的存活对象导致的空间碎片化问题就只能依赖更为复杂的内存分配器和内存访问器来解决。内存的访问是用户程序最频繁的操作,甚至都没有之一,假如在这个环节上增加了额外的负担,势必会直接影响应用程序的吞吐量。

从垃圾收集的停顿时间来看,不移动对象停顿时间会更短,甚至可以不需要停顿

但是从整个程序的吞吐量来看,移动对象会更划算

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值