JVM探秘之GC

来源:互联网 发布:高中英语在线听力软件 编辑:程序博客网 时间:2024/06/06 13:32

GC的三个问题(what,when,how),带着问题去思考。

哪些内存需要回收?

  程序计数器、虚拟机栈、本地方法栈3个区域随线程而生死,所需要的内存大小基本是在编译期间确定下来的,一旦线程或方法结束,内存也就跟着回收了,所以这几个区域不需要过多的考虑垃圾回收问题。但是java堆和方法区只有在程序运行时才能确切的知道需要哪些对象需要多少内存,这部分的内存分配和回收都是动态的,所以这部分内存才是需要垃圾收集器主要关注的点。

什么时候回收?

  能想到的是当堆中的对象“死“了的时候,就该对死去的对象进行回收了,但是如何判断一个对象是否“死亡”,或者说如何判断对象不可能再被任何对象或途径引用?于是,机智的前辈们设计了如下的算法判断对象是否“死亡。”

  1. 引用计数算法

      给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
      引用计数算法实现简单,判定效率也高,但是这个算法最大的问题是很难解决对象之间相互循环引用。对象之间相互引用,导致对象的引用计数器都不为0,进而无法通知垃圾收集器进行回收。显然,jvm是不会采用这种算法去判断对象是否“死亡”。

  2. 可达性分析算法

      这个算法的基本思想是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象不可用的,说明该对象已“死”。jvm是采用这种方式来判定对象是否存活的。
      值得注意的是,在可达性分析算法中不可达的对象,并不是立即“处死”,要真正宣告一个对象的“死刑”,至少要经历两次标记的过程:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”,即对象被“处死”。

如何回收?

  1. 标记-清除算法

      顾名思义,标记后再清除。算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。主要的不足有两个:一是效率问题,标记和清楚两个过程效率都不高;另一个是空间问题,标记清楚后会产生大量的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
    这里写图片描述
            标记-清楚 示意图(图来自《深入理解java虚拟机》)

  2. 复制算法

      为了解决标记清楚算法产生的效率问题,复制算法应运而生。它把可以使用的内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况。只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。这种算法的代价是将内存缩小为原来的一半。

这里写图片描述
      复制算法示意图(图来自《深入理解java虚拟机》)

  实际的商业虚拟机中如HotSpot,并不是按1:1的比例划分内存空间,而是将内存分为一块较大的Eden空间和两个较小的Survivor空间,每次使用Eden和其中一块Survivor,当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。复制算法主要应用在新生代中。

3. 标记-整理算法

  由于复制算法在对象存活率较高的情况下就要进行较多的复制操作,效率将会降低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
  标记-整理算法标记过程与标记-清除算法一样,但是后续不是对回收对象直接清理,而是把存活的对象移向一端,然后再清理端边界以外的内存。

  当前商业虚拟机的垃圾收集都采用“分代收集”算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。

0 0
原创粉丝点击