深入JVM垃圾回收算法

1.如何确定某个对象是“垃圾”?

这一小节先了解一个最基本的问题:如果确定某个对象是“垃圾”?既然垃圾收集器的任务是回收垃圾对象所占的空间供新的对象使用,那么垃圾收集器如何确定某个对象是“垃圾”?通过什么方法判断一个对象可以被回收了。

在java中是通过引用来和对象进行关联的,也就是说如果要操作对象,必须通过引用来进行。那么很显然一个简单的办法就是通过引用计数来判断一个对象是否可以被回收。不失一般性,如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到,那么这个对象就成为可被回收的对象了。这种方式成为引用计数法。

这种方式的特点是实现简单,而且效率较高,但是它无法解决循环引用的问题,因此在Java中并没有采用这种方式(Python采用的是引用计数法)。

为了解决这个问题,在Java中采取了可达性分析法。该方法的基本思想是通过一系列的“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。
 

在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是开始进行垃圾回收,但是这里面涉及到一个问题是:如何高效地进行垃圾回收。由于Java虚拟机规范并没有对如何实现垃圾收集器做出明确的规定,因此各个厂商的虚拟机可以采用不同的方式来实现垃圾收集器,所以在此只讨论几种常见的垃圾收集算法的核心思想。


2.经典的回收算法

1. 标记——清除算法(Mark-Sweep)

标记——清除算法是第一种使用和比较完善的垃圾回收算法,算法分为两个过程:1、标记正所有需要回收的对象2、标记完成后清除被标记的对象。其标记的过程就是判断对象有效性,执行可达性分析的过程。其执行流程图大体如何下所示: 


同样,我们也借助现实的场景进行描述。图书管里有好多人在看书,图书管理员想要收集起没有被看的书的时候,他决定让所有正在看书的人站起来,然后询问每个人:那一本书是不看的。询问完所有的人之后,同学们做下继续看书。这时候,图书管理员开始寻找所有做过标记的书,把它们收集起来。

缺点: 
1、每次进行垃圾回收时,会暂停当前用户程序的运行(类似让所有的同学站起来) 
2、垃圾回收器需要间隔性的检查,并且标记和清除的过程相对较慢。 
3、在标记清除之后可能会产生大量内存碎片,导致一旦需要为大对象分配空间时,由于找不到足够大的内存空间,而不得以引发另外一次GC过程。

2. 标记——复制算法(Mark——Copy)

标记——复制存储算法通过采用双区域交替使用这种方式解决了标记——清除算法中效率低下的问题。它将可可用内存划分为两个等量的区域(使用区和空闲区),每次只使用一块。当正在使用的区域需要进行垃圾回收时,存活的对象将被复制到另外一块区域。原先被使用的区域被重置,转为空闲区。其执行流程大体如下所示: 
 


图书管理员为了更好的发现不看的书,将图书室一分为二(A区和B区),同一时刻只有一块区域允许看书。开始时只允许在A区看书。当管理员想要回收A区不被看的书的时候,大喊一嗓子“正在看书的同学拿着你书到B区”。等所有人都到了B区后,图书管理员只要把A区的书收集起来,就完成了任务。下一次收集的时候,则是要求同学带着自己看的书从B区转移到A区。如此循环往复即可。

缺点: 
1、原有可用空间被缩小为1/2,空间利用率降低了。 
2、过程中也会暂停当前应用的运行。

3. 标记——整理算法(Mark——Compat)

标记-复制算法在对象存活率较高的情况下就要进行较多的复制操作,更重要的是该算法浪费一般的内存空间,为了解决该问题,出现了标记——整理算法:其标记的过程和“标记-清除”算法一样,而整理的过程则是让所有存活的对象都想另外一端移动,然后直接清理掉端边界以外的内存。其执行流程大体如下所示: 

Mark-Compact
缺点: 
1、暂停当前应用的运行,非实时性的回收。

4. 分代收集算法

分代收集算法理论来源于统计学。IBM公司的专门研究发现,对象的生存周期总体可分为三种:新生代、老年代和永久代。因此可以根据各个年代的特点采用适当的垃圾回收算法。比如新生代的对象在每次垃圾时都会有大量的对象死去,只有很少一部分存活,那就可以选择标记-复制算法。另外I,在新生代中每次死亡对象约占98%,那么在标记-复制算法中就不需要按照1:1的比例来划分内存区域,而是将新生代细分为了一块较大的Eden和两块较小的Survivor区域,HotSpot中默认这两块区域的大小比例为8:2。每次新生代可用区域为Eden加上其中一块Survivor区域,共90%的内存空间,这样就只有10%的内存空间处在被闲置状态。在进行垃圾回收时,存活的对象被转移到原本处在“空闲的”Survivor区域。如果某次垃圾回收后,存活对象所占空间远大于这10%的内存空间时,也就是Survivor空间不够用时,需要额外的空间来担保,通常是将这些对象转移到老年代。

对于老年代来说,大部分对象都处在存活状态。同时,如果一个大对象要在该区域进行分配,而内存空间又不足,那么在没有外部内存空间担保的情况下,就必须选用标记-清除或者标记-整理算法来进行垃圾回收了。

总而言之,分代收集只是根据对象生存周期的不同来选择不同的算法,其本身并没有任何新思想。


5. 增量收集算法

以上所述的算法,都存在一个缺点:在进行垃圾回首时需要暂停当前应用的执行,也就是这时候的垃圾回收线程不能和应用线程同时运行。如果我们想做到“在不打断同学们看书的情况下,图书管理员就可以收集没有被看的书”,这也是增量收集算法的目标,即在不中断应用线程的状态下垃圾回收线程也能进行垃圾回收。但是这里需要面对的问题是:垃圾回收线程在标记阶段标记好了,还没来的及清除时,当前应用线程进行内存操作,以至于清除阶段无法正确开展,类似的情况是:图书管理员刚标记了《JAVA核心技术》这本书已经没有人看了,等标记完后,却发现这本书已经有人在看了。

增量算法基本思想是:如果一次性将所有垃圾进行处理,则会造成系统长时间的停顿,那么可以让垃圾回收的线程和应用程序的线程交替执行。垃圾回收只是回收一小块内存,接着切换到应用程序线程。这样就减少了系统的停顿时间。因为线程的切换和上下文的转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量下降 。
       增量收集算法的技术是标记清楚和整理,只是允许垃圾回收进程以阶段完成标记、清理、或复制工作。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值