Java垃圾回收(二)垃圾收集算法---深入理解Java虚拟机

来源:互联网 发布:杂志订阅 知乎 编辑:程序博客网 时间:2024/04/28 03:00

垃圾收集算法
由于垃圾收集算法的实现设计大量的程序细节,而且各个平台的虚拟机操作内存的方法又不相同,因此本节只是介绍几种算法的思想及其发展过程
1. 标记-清除(Mark-Sweep)算法
标记清除
最基础的算法是“标记-清除”算法,就和它的名字一样,算法分为两个阶段“标记”和“清除”,该算法的标记阶段就是我们上一章说的判断对象存活状态,我们在判断完对象的状态之后就会进行标记,如果对象死亡,将会被标记成可回收的状态,然后在标记阶段完成之后,就会对被标记的对象进行回收。之所以说它是基础的算法,是因为它具有明显的不足,而后面有其他的算法在这个基础上进行完善。
标记-清除算法的两个主要不足:
一个是效率问题,标记和清除两个过程的效率都不高;
另一个是空间的问题,在标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要大的内存,因为无法找到足够的连续内存而不得不提前出发另一次垃圾收集动作。

  1. 复制收集算法(主要进行新生代的垃圾收集)
    这里写图片描述
    为了解决效率问题,一种称为“复制”的算法出现了,,它将可用内存按容量划分为大小相等的两块,每次使用其中的一块,当这块内存用完了,就将还存活的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也不用考虑内存碎片等情况,只要移动堆指针,按顺序分配内存即可,实现简单,运行高效。这种算法的代价就是讲内存缩小为原来的一半,这个代价有点大。

  2. 标记-整理算法(主要进行老年代的垃圾收集)
    标记-整理算法

    复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会降低,更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,所以在老年代中一般不直接选用这种算法。标记-整理算法中的标记过程仍然和“标记-清理”算法一样,但是后续步骤不是直接对可回收对象进行整理,而是让所有存活的对象向一端移动,然后清理掉端边界以外的内存。
  3. 分代收集算法
    分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。

      目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。

      而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。

      注意,在堆区之外还有一个代就是永久代(Permanet Generation),它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类

HotSpot的算法实现

枚举根节点    从可达性分析中从GC Roots节点找引用链这个操作为例,可作为GC Roots的节点主要在全局性的引用(例如常量或类静态属性)与执行上下文(例如栈帧中的本地变量表)中,现在很多应用仅仅方法区就有数百兆,如果要逐个检查这里面的引用,那么必然会消耗很多时间。    另外,可达性分析对执行时间的敏感还体现在GC停顿上,因为这项分析工作必须能确保一致性的快照中进行--这里“一致性”的意思是指在整个分析期间整个执行系统看起来就像被冻结在某个时间,不会出现分析过程中对象引用关系还在不断变化的情况。这点事导致GC进行时必须停顿所有Java执行线程(Stop the World)。    在HotSpot中使用一组称为OopMap的数据结构用来存储上下文中,有效的引用对象的位置,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,然后记录在OopMap上,然后我们可以通过检查这些OopMap内的数据进行判断有效数据。安全点    HotSpot中并没有为每一条指令都生成OopMap,只有在特定的位置才会记录这些信息,我们把这种位置称为 安全点,即程序执行时并非在所有地方都能停顿下来GC,只有在到达安全点时才能暂停。只有在“长时间执行”的最明显特征就是指令序列复用,例如方法调用,循环跳转、异常跳转等,所以具有这些功能指令才会产生safepoint    抢先式中断和主动式中断    抢先式中断就是先把所有线程中断,如果发现有线程不在安全点,就让他跑过去安全点(现在几乎没有虚拟机采用)    主动式中断:    不直接对线程操作,仅仅简单地设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真的时候就自己中断挂起。    安全区域        安全区域是指在一段代码片段中,引用关系不会发生变化,这个区域汇总的任意地方都是安全的,主要面对线程处于sleep或者Blocked状态
0 0