JVM 垃圾检测、回收算法

来源:互联网 发布:宏金赛鸽公棚扫猫数据 编辑:程序博客网 时间:2024/06/04 20:52


三、垃圾检测、回收算法

垃圾收集器一般必须完成两件事:检测出垃圾;回收垃圾




怎么检测出垃圾?一般有以下几种方法:


(1)引用计数算法:给一个对象(堆中的对象)添加 【引用计数器】,每当有个地方引用它,计数器就加1;引用失效就减1。计数为0,进行回收操作。好了,问题来了,如果我有两个对象A和B,互相引用,除此之外,没有其他任何对象引用它们,实际上这两个对象已经无法访问,即是我们说的垃圾对象。但是互相引用,计数不为0,导致无法回收。所以还有另一种方法:


(2)可达性分析算法:根集对象为起始点进行搜索,如果有对象不可达的话,即是垃圾对象。

         这里的根集一般包括【java栈中引用的对象】、【方法区常量池中引用的对象】,【本地方法中引用的对象】等。

         总之,JVM在做垃圾回收的时候,会检查堆中的所有对象是否会被这些根集对象引用,不能够被引用的对象 就会被  垃圾收集器  回收。



============================================================================================================



回收算法?一般有以下几种方法:



1、引用计数(reference counting)



2.标记-清除(Mark-sweep)


算法和名字一样,分为两个阶段:标记和清除。标记所有需要回收的对象,然后统一回收。这是最基础的算法,后续的收集算法都是基于这个算法扩展的。

不足:效率低;标记清除之后会产生大量碎片。效果图如下:




2.复制(Copying)


此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把【正在使用中的对象】复制到另外一个区域中。此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。效果图如下:







3.标记-整理(Mark-Compact) 也叫 : 标记-压缩                                     (Compact :压缩)



此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。效果图如下:


(1,2,3 图文摘自 http://pengjiaheng.iteye.com/blog/520228,感谢原作者。)





4.分代收集算法 


这是当前商业虚拟机常用的垃圾收集算法。


JavaHeap分为3个区,Young,Old  和  Permanent。


分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。


因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。


为什么要运用分代垃圾回收策略?

在java程序运行的过程中,会产生大量的对象,因每个对象所能承担的职责不同所具有的功能不同所以也有着不一样的生命周期,

有的对象生命周期较长,比如Http请求中的Session对象,线程,Socket连接等;

有的对象生命周期较短,比如String对象,由于其不变类的特性,有的在使用一次后即可回收。


试想,在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行扫描回收,那么消耗的时间相对会很长,而且对于存活时间较长的对象进行的扫描工作等都是徒劳。因此就需要引入分治的思想,所谓分治的思想就是因地制宜,将对象进行代的划分,把不同生命周期的对象放在不同的代上使用不同的垃圾回收方式。


=========================如何划分?=====================================

java Heap分为3个区,Young,Old和Permanent。







将对象按其生命周期的不同划分成:年轻代(Young Generation)、年老代(Old Generation)、持久代(Permanent Generation)。

其中持久代主要存放的是类信息,所以与java对象的回收关系不大,与回收息息相关的是年轻代和年老代。


这里有个比喻很形象,“假设你是一个普通的 Java 对象,你出生在 Eden 区,在 Eden 区有许多和你差不多的小兄弟、小姐妹,可以把 Eden 区当成幼儿园,在这个幼儿园里大家玩了很长时间。Eden 区不能无休止地放你们在里面,所以当年纪稍大,你就要被送到学校去上学,这里假设从小学到高中都称为 Survivor 区。开始的时候你在 Survivor 区里面划分出来的的“From”区,读到高年级了,就进了 Survivor 区的“To”区,中间由于学习成绩不稳定,还经常来回折腾。直到你 18 岁的时候,高中毕业了,该去社会上闯闯了。

         于是你就去了年老代,年老代里面人也很多。在年老代里,你生活了 20 年 (每次 GC 加一岁),最后寿终正寝,被 GC 回收。有一点没有提,你在年老代遇到了一个同学,他的名字叫爱德华 (慕光之城里的帅哥吸血鬼),他以及他的家族永远不会死,那么他们就生活在永生代。”




年轻代:是所有新对象产生的地方。年轻代被分为3个部分——Enden区和两个Survivor区(From和to)。

当Eden区被对象填满时,就会执行【Minor GC】。并把所有 【存活下来的】 对象转移到其中一个survivor区(假设为from区)。

【Minor GC】同样会检查存活下来的对象,并把【存活下来的】 转移到另一个survivor区(假设为to区)。

这样在一段时间内,总会有一个空的survivor区。

经过多次GC周期后,仍然存活下来的对象会被转移到年老代内存空间。通常这是在年轻代有资格提升到年老代前通过设定年龄阈值来完成的。


需要注意,Survivor的两个区是对称的,没先后关系,from和to是相对的。


-----------------------------------------------------------------------------------------------------------------------------------------------------------------------






-----------------------------------------------------------------------------------------------------------------------------------------------------------------------



年老代:在年轻代中经历了N次回收后仍然没有被清除的对象,就会被放到年老代中,可以说他们都是久经沙场而不亡的一代,都是生命周期较长的对象。对于年老代和永久代,就不能再采用像年轻代中那样搬移腾挪的回收算法,因为那些对于这些回收战场上的老兵来说是小儿科。通常会在老年代内存被占满时将会触发Full GC,回收整个堆内存。


持久代:用于存放静态文件,比如java类、方法等。持久代对垃圾回收没有显著的影响。 


分代回收的效果图如下:


分代里涉及了前面几种算法:


            年轻代:涉及了复制算法;

            年老代:涉及了“标记-整理(Mark-Sweep)”的算法 或  标记清除算法。


目前商用虚拟机都使用“分代收集算法”,所谓分代就是根据对象的生命周期把内存分为几块,一般把Java堆中分为新生代和老年代,这样就可以根据对象的“年龄”选择合适的垃圾回收算法。



垃圾回收时:


             新生代对象中都会有大批量的对象死亡,就选择复制算法(因为存活的对象较少,而死亡的对象过多,如果使用标记-清除算法的话,需要遍历标记,显然效率较低,而使用复制算法就可以把存活的较少的对象复制到可用内存区域中,这样效率就较高);


             对于老年代对象,其存活率较高,所以就可以使用 “标记-整理”算法。或 标记清除算法。





四、垃圾收集器


垃圾收集算法是内存回收的方法论,而实现这些方法论的则是垃圾收集器。

不同厂商不同版本JVM所提供的垃圾收集器可能不同,这里参照《深入理解Java虚拟机》说的是JDK1.7版本Hotspot虚拟机,关于垃圾收集器有篇博文总结的不错,我就不说了,详见:http://blog.csdn.net/java2000_wl/article/details/8030172


原创粉丝点击