美文网首页
Java虚拟机垃圾收集

Java虚拟机垃圾收集

作者: LeonardoEzio | 来源:发表于2019-01-29 16:43 被阅读0次

从上一篇文章《JVM内存模型相关基本概念》中我们可以了解到,java内存运行时区域中的程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭。在这几个区域内就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而堆和方法区则不同,一个接口的实现是多种多样的,多个实现类需要的内存可能不一样,一个方法中多个分支需要的内存也不一样,我们只能在程序运行的期间知道需要创建那些对象,分配多少内存,这部分的内存分配和回收都是动态的,垃圾收集器所关注的也是这一部分内容,后续所说的“内存”分配与回收也是指这一部分的内在。

垃圾回收

1. 如何判断对象已死

垃圾收集器在对堆进行回收之前,第一件事情就是要确定哪些对象还活着,哪些对象已经死去可以进行回收。判断对象是否存活主要有以下方法:

  • 引用计数法:
    给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;当计数器值为0时,就表明这个对象已经死亡(java虚拟机中并没有采用这种方式来管理内存,因为其不能有效的解决对象之间的相互引用的问题)。

  • 可达性分析算法:
    可达性分析引入了GC Root的概念,以GC Root的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时,则证明这个对象是不可用的已经死亡,此对象将会被虚拟机判定为是可回收的对象。

可达性分析算法判定对象是否存活.png

在Java语言中,可作为GC Roots的对象包括下面几种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  2. 方法区中类静态属性引用的对象。
  3. 方法区中常量引用的对象。
  4. 本地方法栈中JNI(Native 方法)引用的对象。

2. Java中对引用的定义

在JDK1.2之前,Java中的引用的定义很传统:如果reference类型的数据中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用。JDK1.2之后,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种,四种引用强度依次逐渐减弱。

  • 强引用: 程序中普遍存在的,类似 Object obj = new Object () ;这类的引用,只要强引用存在,垃圾收集器永远不会回收掉被引用的对象。

  • 软引用: 描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。

  • 弱引用: 也是用来描述有用但并非必需对象的,但它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

  • 虚引用:也被称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引来取得对象的实例,虚引用的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

详情参考:java中的四种引用类型


3. Finalize方法(一个有趣的对象自救案例)

要真正宣告一个对象死亡,至少要经历两次标记过程;如果对象在进行可达性分析后发现没有存在与GC Root相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是对象是否有必要执行finalize()方法。当对象没有覆盖,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为没有必要执行。

public class ObjectSaveTest {

    public static ObjectSaveTest SAVE_HOOK = null;

    public void isAlive (){
        System.out.println("yes , i am still alive");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize is executed!!");
        SAVE_HOOK =  this;
    }

    public static void main(String[] args) throws Exception{
        SAVE_HOOK = new ObjectSaveTest();

        //对象第一次成功拯救自己   在finalize方法中,重新关联上引用
        SAVE_HOOK = null;
        System.gc();

        Thread.sleep(500);

        if(SAVE_HOOK != null){
            SAVE_HOOK.isAlive();
        }else {
            System.out.println("no , i am dead :( ");
        }


        //对象第一次未能成功拯救自己
        SAVE_HOOK = null;
        System.gc();

        Thread.sleep(500);

        if(SAVE_HOOK != null){
            SAVE_HOOK.isAlive();
        }else {
            System.out.println("no , i am dead :( ");
        }
    }
}
4. 回收方法区

方法区(HotSpot虚拟机中的永久代)的垃圾收集主要回收两部分的内容:废弃常量和无用的类。

  • 废弃常量:假如常量池中存在一个字符串abc,如果当前系统没有任何Stirng对象引用常量池的abc常量,也没有其他地方引用了这个字面量,这个时候发生内存回收这个常量就会被清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也于此类似。

  • 无用的类:满足以下三天条件的类才能算是无用的类。

    1. 该类所有的实例都已经被回收,就是Java堆中不存在该类的任何实例。

    2. 加载该类的ClassLoader已经被回收。

    3. 该类对用的java.lang.Class对象没有在任何地方被引用,无法再任何地方通过反射访问该类的方法。


5. 垃圾收集算法
  • 标记—清除算法(Mark-Sweep):
    算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的
    对象。

    它的不足主要体现在两个方面:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问
    题,标记清楚之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后再程序运行过程中需要
    分配较大的对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

  • 复制算法(Copying):
    复制算法将可用内存按照容量划分为大小相等的两块,每次只使用其中的一块。当这块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可。

    它的不足主要体现在将内存缩小为了原来的一半。

    大部分的商业虚拟机的新生代都是采用复制算法来进行垃圾回收的,新生代中的内存空间主要划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor空间。当回收时,将Eden和Survivor中还存活着的对象复制到另一块Survivor空间上,最后再清理掉Eden和刚才的Survivor空间。HotSpot虚拟机默认 Eden和Survivor的大小比例为8:1。但在实际垃圾收集过程中会产生这样一个问题,当另一块Survivor空间不足以存放上一次新生代收集下来的存活对象时,这时候就需要借助老年代的内存进行担保分配,这些对象将通过分配担保机制直接进入老年代区域。

  • 标记—整理算法(Mark-Compact):
    复制算法在对象存活率较高的情况下就要进行较多的复制操作,效率将会变低。而JVM的老年代区域主要用来存放应用程序中生命周期长的内存对象,因此不适合采用复制算法。根据老年代的特点产生了标记整理算法,让所有存活着的对象都向一端移动,然后直接清理掉端边界以外的内存。这样做的好处是不会产生大量不连续的内存空间,适合老年代的大对象存储。

  • 分代收集算法:
    根据对象存活周期的不同将内存划分为几块。一般是把java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。

    在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法。只需要付出少量存活对象的复制成本就可以完成收集。

    老年代中因为对象存活率高、没有额外空间对他进行分配担保,就必须用标记-清除或者标记-整理。


6. 垃圾收集器
  • Serial收集器:
    Serial收集器是一个单线程的收集器,它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更为重要的是它在进行垃圾收集时,必须暂停其他其他所有的工作线程,直到其收集结束。
Serial收集器
  • ParNew收集器:
    ParNew收集器其实就是Serial收集器的多线程版本,除了使用了多线程进行收集之外,其余行为和Serial收集器一样。

    ParNew收集器
  • Parallel Scavenge 收集器:
    Parallel Scavenge收集器是一个使用复制算法的新生代并行多线程收集器,其提供了可控制的吞吐量(就是CPU用于运行用户代码的时间与CPU总消耗时间的比值。即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间))。由于与吞吐量关系密切,它也被称为“吞吐量优先”收集器。

  • Serial Old收集器:
    Serial Old是Serial收集器的老年代版本,同样也是一个单线程的,采用标记整理算法的收集器。

  • Parallel Old收集器:
    Parallel Scavenge收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器。

  • CMS收集器:
    CMS(Concurrent Mark Sweep)收集器以追求最短回收停顿时间为目标,基于“标记—清楚”算法实现。其整个运作过程主要分为以下4步:

    1. 初始标记(Initial mark):暂停所有的其他线程,并记录下直接与GC Root相关联到的对象,速度很快 ;
    2. 并发标记(concurrent mark):同时开启GC和用户线程,进行GC Roots Tracing的过程。
    3. 重新标记(remark):修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。
    4. 并发清除(concurrent sweep):开启用户线程,同时GC线程开始对为标记的区域做清扫。


      CMS收集器

由于CMS收集器整个工作过程中耗时最长的并发标记与并发清楚阶段都可以与用户线程同时工作(初始标记,重新标记依然要Stop the world),所以从总体上看:CMS收集器的内存回收过程是和用户线程一起并发执行的,因此CMS收集器也拥有了并发收集,低停顿的优点。同时它也存在着以下三个明显的缺点:

  1. 对CPU资源敏感;
  2. 无法处理浮动垃圾;
  3. 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
  • G1收集器:
    G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器.,以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。它主要具备以下特征:
    1. 并行与并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
    2. 分代收集:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。
    3. 空间整合:与CMS的“标记--清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
    4. 可预测的停顿:这是G1相对于CMS的另一个大优势,降低停顿时间是G1 和 CMS 共同的关注点,但G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内。

G1收集器的运作大致分为初始标记、并发标记、最终标记、筛选回收这几个步骤。收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region(这也就是它的名字Garbage-First的由来)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了GF收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。

上一篇:JVM内存模型相关基本概念
下一篇:Java虚拟机内存分配与回收策略

相关文章

网友评论

      本文标题:Java虚拟机垃圾收集

      本文链接:https://www.haomeiwen.com/subject/qaexsqtx.html