深入理解Java虚拟机之垃圾收集算法篇

2018-03-17  本文已影响0人  Michaelhbjian

这篇文章将讲解垃圾回收的概念以及对那些区域进行垃圾回收,最后讲解几种常见的垃圾回收算法。

概述

什么叫垃圾收集器?

需要思考GC需要完成的3件事情:

下面介绍一下Java内存运行时区域的各个部分,为什么有些区域需要回收,有些区域不需要回收?以及怎么去回收?

对象已死吗?

在堆里面存放着Java世界中几乎所有的对象实例,垃圾收集器在堆进行回收前,第一件事情就是确定这些对象之中哪些还“存活”着,哪些已经“死去”(即不能再被任何途径使用的对象)

引用计数算法

很多教科书判断对象是否存活的算法是这样的:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器就减1;任何时刻计数器为0的对象就是不可能在被使用的。

可达性分析算法

通过可达性分析算法(Reachability Analysis)来判定对象是否存活的。

基本思路:通过一系列的称为GC Roots 的对象作为起始点,从这些起始点往下搜索,搜索走过的路径称为引用链,当一个对象和GC Roots没有任何引用链(即GC Roots到这个对象时不可达的),说明对象时无用的。

[图片上传失败...(image-bcae71-1521271966954)]

在Java中可作为GC Roots的对象有下面几种:

再谈引用

无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与引用有关。

引用的四种类型:

生存还是死亡

可达性分析法中不可达的对象也不是非死不可的,而是处于缓刑阶段。要宣告一个对象的死亡至少要经过两次标记过程:

如果这个对象有必要执行finalize方法,那么对象会被放在F-Queue的队列中,并且会被由Java虚拟机自动创建的、低优先级的Finalizer线程去执行。finalize方法是对象最后一次逃脱死亡的机会,在finalize方法后,GC将会对对象进行第二次标记。如果对象在finalize方法中成功拯救自己,那么在第二次标记时会被移出回收集合,否则就真的被回收了。

回收方法区

很多人认为方法区(虚拟机中的永久代)是没有垃圾回收的,Java虚拟机规范也确实说过不要求虚拟机在方法区实现圾回收,因为方法区的垃圾收集效率很低。

方法区的垃圾收集主要回收两部分内容:废弃常量无用的类

垃圾收集算法

标记-清除算法

工作原理:算法主要分为两个阶段标记和清除,首先标记出所有需要回收的对象,标记完成后统一进行清除。

img

缺点:

复制算法

为解决效率问题,复制算法出现了:它将内存空间分为大小相等的两块区域,每次只使用其中一块,当进行垃圾收集时,将这块区域中还存活的对象复制到另一块,然后将这一块内存回收。这样就不会产生内存碎片的问题。

img

缺点:这种算法实现简单,运行高效,只是代价是每次只能使用内存的一半,代价过高。

现在的商用虚拟机都采用这种收集算法回收新生代内存。根IBM公司的研究表明,新生代中的内存对象98%是朝生夕死的,所以不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden区域,两块较小的Survivor区域。每次只使用一块Eden区域和一块Survivor区域,当进行垃圾收集时,将Eden区域和Survivor区域仍然存活的对象复制到另一块Survivor区域,然后将Eden区域和使用过的Survivor区域清除HotSpot虚拟机默认的Eden和Survivor区域大小比例为8:1,这样只会浪费10%的内存。

标记-整理算法

复制算法在对象成活率较低的新生代比较适用,而对于对象成活率较高的老年代就需要进行较多的复制操作,效率明显会减低。所以针对老年代的特点,提出了标记-整理算法:标记清除过程仍然与标记清楚算法一样,只是在清除后将存活的对象都向一端移动。

img

分代收集算法

当前商业的虚拟机的垃圾收集算法都采用分代收集算法:根据对象存活周期的不同将内存划分为几块,一般把Java分为新生代和老年代,在根据各个年代的特点选择合适的收集算法。在新生代中,对象存活率低,适合使用复制算法,而老年代对象存活率较高,适合使用标记清除算法或标记-整理算法。

参考资料

https://juejin.im/post/5aa0e8176fb9a028d663be1e

http://www.ityouknow.com/jvm/2017/08/29/GC-garbage-collection.html

上一篇下一篇

猜你喜欢

热点阅读