垃圾回收系列(3):CLR与JVM垃圾回收器的比较

本文为垃圾回收讲座的第三篇,在前面两篇( )文章里介绍了手工管理内存带来的一些问题,以及一些经典的GC算法。本文我们主要关注微软的CLR与JVM垃圾回收器方面的比较。我们知道CLR和JVM都采用了分代式垃圾回收器,而分代式垃圾回收器则基于以下几点假设:

1. 对象越新,其生存期就越短
2. 对象越老,其生存期就越长
3. 对堆的一部分执行GC比对整个堆执行GC要快

CLR和JVM尽管都采用了分代式垃圾回收器,但是它们在很多处理方面都有些不同:分代机制,大对象堆,回收模式,回收算法,寻找存活对象效率等。

分代机制

在CLR中,对象按年龄可以分为三代:第0代、第1代、第2代,如下图所示:

gc020  

在这三代之间,对象代的提升过程,大家可以参考《CLR via C# 》,里面有比较详细的介绍。

JVM中对于对象的分代式新生代和旧生代:

gc021

回收模式

在CLR4.0之前,提供三种不同的垃圾回收模式:工作站并发GC、工作站非并发GC以及服务器GC,如下图所示:

alt

工作站非并发GC模式,没有专门的GC线程,而是由工作线程负责回收,在回收过程中,需要暂时挂起应用程序,回收结束后应用程序继续运行,所以在回收过程中会有应用程序暂停现象:

alt

工作站并发GC模式,为了解决在执行垃圾回收时引起的应用程序暂停问题,会有一个专门的GC线程负责垃圾回收,大多数时间垃圾回收都可以应用程序并 发执行,但是仅仅是针对Full GC,而对于第0代、第1代对象,仍然会使用非并发模式执行,并发垃圾回收本质上牺牲了更多的CPU时间和内存来换取应用程序暂停时间的减小:

 

 

 

alt

服务器GC模式运行在多CPU服务器上,如果在单CPU机器上配置了使用服务器GC,不会起任何作用,垃圾回收仍然会使用工作站非并发模式执行。服 务器GC模式为每个CPU分配一个专用的垃圾回收线程和一个托管堆,并且该垃圾回收线程具有较高的优先级,在执行垃圾回收期间,应用程序工作线程会暂时挂 起:

alt

CLR 4.0中提供了后台垃圾回收机制,用于取代并发GC。

JVM(以Hotspot为例)中使用的垃圾回收更为复杂,针对新生代、旧生代在工作站和服务器上,分别使用不同的垃圾回收模式,如下图所示:

alt

在Client端默认的方式为串行GC,而在服务端,对于新生代和旧生代默认的方式分别为:并行回收GC和并行GC:

alt

下图体现了默认串行GC与并行GC之前的区别,并行GC会把堆分成多个区,分区进行标记和回收,但这两种方式都会引起应用程序的暂停:

alt

下图体现了默认的标记缩并回收与并发GC,在并发GC中,标记的总共分为三个阶段,分别为:Initial Mark、Concurrent Marking和Remark,只有在Initial Mark和Remark阶段才会引起应用程序暂停,而在Concurrent Marking和清除阶段都是与应用程序并发执行,并不会引起暂停:

alt

回收算法

在CLR中有专门的大对象堆(LOH),超过85000字节的对象将会分配在LOH上面,只有在进行第2代对象垃圾回收时才会同时对LOH进行回收,即一次Full GC。第0代、第1代、第2代对象所在的堆称之为小对象堆(SOH)。

在CLR中,对于小对象堆SOH,采用的回收算法为标记-缩并算法 ,由于移动大对象比较耗费时间,所以在LOH上,CLR采用了标记-清除算法 ,即只做对象的清除并不对大对象堆进行压缩。

在JVM中,对于新生代对象采用节点复制算法 进行回收,这也是为什么我们在上面的图中,新生代对象堆分为S0和S1的原因,有时也称之为为From和To;对于旧生代对象根据不同的回收模式,采用不同的回收算法:

串行GC:标记-缩并算法(滑动缩并)
并行GC:标记-缩并算法
并发GC:标记-清除算法

提高查找存活对象的效率

现在考虑这样一个问题,只对第0代(或者新生代)对象做回收,在查找存活对象过程中(不关是标记-缩并还是节点复制算法),从根集合开始,如果发现有一个根对象指向了第1代、第2代(或者旧生代),为了提高效率,垃圾回收器将会立即终止这条线上的查找:

alt

这样带来的一个问题是:如果在第0代(或者新生代)分配的对象X,没有根对象指向它,但有一个第1代或者第2代(旧生代)的对象,指向了该新创建的对象:

alt

此时虽然没有根对象可以到达新创建的对象X,但由于有其他代的对象指向它,所以它仍然不能当做垃圾回收,为了解决这个问题,人们提出了各种解决方案,常见的有:

1. 记忆集
2. 硬件标记
3. 虚拟页面标记
4. 字标记
5. 卡标记

由于硬件标记需要特定的硬件平台来支持,所以不具有可移植性,现在更多的垃圾回收器都使用了软件解决方案,在CLR和JVM上都使用了卡标记技术, 即把托管堆分成一个一个的卡片,这个卡片代表一个抽象的概念(卡片的大小可以等于一个字,也可以等于一个虚拟页面)。如果有卡片上的对象发生了变化,则在 Card Table中做一个标记,在查找存活对象时,除了要查找根对象之外,还有查找Card Table中标记的对象所引用的对象:

alt

在CLR和JVM上所使用的卡片大小也不相同,CLR为128字节一个卡片,而JVM上为512字节一个卡片。在更新卡片过程中,它们都是按字节(Byte)进行更新而不是按位(Bit)进行更新。

总结

虽然CLR和JVM都采用了分代式垃圾回收器,但是它们在很多处理上都有区别,不管怎样,分代式垃圾回收器的一个原则就是把对象分成不同的区(按年龄也好、按对象大小也好),以便在不同的分区上采用不同的回收算法和模式。本系列前两篇文章链接:

1. 垃圾回收系列(1):没有GC,世界将会怎样

2. 垃圾回收系列(2):几种经典的垃圾回收算法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值