Java虚拟机学习(3)—对象存活判定算法和垃圾收集算法

存活下来的物种,并不是最强的和最聪明的,而是最能适应变化的。—— 达尔文 《未知》

一、对象存活判定算法

垃圾收集器在对堆进行回收前,第一件事就是确定哪些对象还“活着”,哪些已经“死了”。

1、引用计数算法

引用计数算法是一个简单且高效的对象存活判定算法:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器数值就加一,当引用失效时,计数器数值就减一。当计数器数值为0时,代表该对象不会再被引用。

尽管这个算法非常通俗易懂,但是并没有什么主流Java虚拟机采用这个算法,主要是因为这个算法会遇到很多特殊状况,必须要进行复杂的处理才能正常的工作。

比如有两个对象A和B,它们互相引用,除此之外没有别的地方引用它们。这种情况这两个对象事实上已经不可能再被访问,但由于它们的计数器数值都为1,所以收集器并不会收集它们。这种情况我们称为“循环引用”。

2、可达性分析算法

当前主流的内存管理子系统,都是通过可达性分析算法来判定对象是否存活的。这个算法的基本思路就是通过一系列成为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径成为“引用链”,如果某个对象到GC Roots间没有任何引用链相连,或者说这个对象到GC Roots间不可达时,说明此对象不可再被访问。

如图所示,对象5、6、7虽然互有关联,但是它们到GC Roots是不可达的,所以他们将会被判定为可回收的对象。

3、引用的类型

无论是引用计数算法还是可达性分析算法都离不开引用的概念。在JDK1.2版本之后,Java对引用的概念进行了扩充,将引用分为以下四种引用,强度依次减弱:

  1. 强引用:即传统的“引用”的定义,任何情况下,只要强引用关系还存在,垃圾收集器就不会回收被引用的对象。
  2. 软引用:用来描述一些还有用,但不是必须的对象。被软引用的对象,在系统将要内存溢出时,会把这些对象列入回收范围之内进行第二次回收,如果第二次回收之后仍然没有足够内存,才会抛出内存溢出异常。使用SoftReference类来实现软引用。
  3. 弱引用:与软引用类似,但强度更低。被弱引用的对象只能存活到下一次垃圾收集,无论内存是否足够,被弱引用的对象都会被回收。使用WeakReference类来实现弱引用。
  4. 虚引用:虚引用是最弱的一种引用,它存不存在并不对对象的生存时间以及访问构成影响,它唯一的作用是在该对象被回收时,使对象能收到一个系统通知。使用PhantomReference类来实现虚引用。

二、垃圾收集算法

1、分代收集理论

当前商业虚拟机的垃圾收集器,大多都遵循了“分代收集”的理论进行设计,分代收集名为理论,实质是一套符合大多数程序运行实际情况的经验法则,它建立在两个分代假说之上:
1)弱分代假说:绝大多数对象都是朝生夕灭的。
2)强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡。

基于这两个分代假说,多款常用的垃圾收集器都达成了一致的设计原则:将Java堆划分成不同的区域,将对象根据其年龄分配到不同的内存区域中存储。显而易见,如果一个区域中大多数对象都是朝生夕灭,那么把它们放在一起,每次回收只关注如何保留少量存活下来的对象,就能以较小代价回收到大量的空间;同样的,将难以消亡的对象放在同一区域,垃圾回收器就可以以较低的频率来回收这个区域,节省了大量系统开销。

设计者一般将Java堆分为新生代和老生代,用来存储年龄不同的对象。针对新生代的垃圾收集,称为“Minor GC”,针对老生代的垃圾收集,称为“Major GC”,而针对整个Java堆和方法区的垃圾收集称为“Full GC”。

2、标记-清除算法

最早出现也是最基础的垃圾收集算法是“标记-清除”算法。算法分为两个阶段,先标记出所有需要回收的对象,再统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收未被标记的对象。

缺点:
1)执行效率不稳定,如果堆中包含大量对象,而且其中大部分是需要回收的,这时就必须进行大量的标记和清除动作,也就是说该算法效率会随着对象数量增长而降低。
2)标记、清除后会产生大量不连续的内存碎片,使得在分配较大对象时无法找到足够的内存空间。

3、标记-复制算法

将内存分为大小相等的两部分,每次只使用其中的一块。当这一块的内存用完了,就将还存活的对象移到另一半内存上去,然后清除这一半的内存空间。

缺点:
1)当保留对象较多时,这种算法将会产生大量的内存间复制的开销。
2)将一半的内存空间作为代价,空间浪费太多了。

改进:
根据研究显示,新生代中的对象有98%熬不过第一轮收集,因此发展出了更优化的半区复制分代策略,称为“Appel式回收”:把新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上,然后清理掉原来两块空间。不过,没有任何人可以担保每次回收都只有不足10%的对象被保留,所以Appel式回收还有一个“逃生门”的安全设计,当Survivor不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域(大多就是老年代)进行分配担保。

4、标记-整理算法

标记整理算法是一种适合老年代使用的收集算法:当垃圾收集时,将所有被保留的对象向内存一侧移动,再将边界以后的所有内存空间清理掉。

标记-清理算法和标记-整理算法的本质区别是是否移动对象。尽管清理对象和移动对象都要暂停用户线程,造成“Stop The World”的问题发生,但是移动对象并改变所有它的引用的值是一项极为负重的操作,因此标记-整理算法在垃圾回收时会造成较大的延迟。

但是如果对内存空间不加以整理,大量空间碎片化的问题就只能依赖更为复杂的内存分配器和内存访问其来解决。而内存访问和对象创建是用户程序最频繁的操作,假如这个环节增加了负担,会大大降低整个程序的吞吐量。因此使用哪种算法不同的虚拟机有不同的选择,一般来说关注延迟选标记-清除算法,关注吞吐量选标记-整理算法。

另外还有一种“和稀泥”的做法,平时多数时间都采用标记-清除算法,暂时容忍内存碎片的存在,直到内存空间的碎片化程度已经大到影响对象分配时,再采用标记-整理算法收集一次,以获得规整的内存空间。

更多精彩文章请访问我的个人博客(zhuoerhuobi.cn)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值