Java GC

作者: BelieveFrank | 来源:发表于2019-05-15 17:30 被阅读0次

    要回收哪些区域

    在JVM五种内存模型中,有三个是不需要进行垃圾回收的:程序计数器、JVM栈、本地方法栈。因为它们的生命周期是和线程同步的,随着线程的销毁,它们占用的内存会自动释放,所以只有方法区和堆需要进行GC。

    如何判断对象已死

    所有的垃圾收集算法都面临同一个问题,那就是找出应用程序不可到达的内存块,将其释放,这里面讲的不可达主要是指应用程序已经没有内存块的引用了, 在Java中,某个对象对应用程序是可到达的是指:这个对象被根(根主要是指类的静态变量,或者活跃在所有线程栈的对象的引用)引用或者对象被另一个可到达的对象引用。

    引用计数算法

    引用计数是最简单直接的一种方式,这种方式在每一个对象中增加一个引用的计数,这个计数代表当前程序有多少个引用引用了此对象,如果此对象的引用计数变为0,那么此对象就可以作为垃圾收集器的目标对象来收集。

    优点:简单,直接,不需要暂停整个应用

    缺点:1.需要编译器的配合,编译器要生成特殊的指令来进行引用计数的操作;2.不能处理循环引用的问题

    因此这种方法是垃圾收集的早期策略,现在很少使用。Sun的JVM并没有采用引用计数算法来进行垃圾回收,而是基于根搜索算法的。

    可达性分析算法

    通过一系列的名为“GC Root”的对象作为起点,从这些节点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时,则该对象不可达,该对象是不可使用的,垃圾收集器将回收其所占的内存。

    在java语言中,可作为GCRoot的对象包括以下几种:

    1.java虚拟机栈(栈帧中的本地变量表)中的引用的对象。

    2.方法区中的类静态属性引用的对象。

    3.方法区中的常量引用的对象。

    4.本地方法栈中JNI本地方法的引用对象。

    常见垃圾收集算法

    标记-清除算法

    步骤:

    1、标记:从根集合开始扫描,标记存活对象;

    2、清除:再次扫描真个内存空间,回收未被标记的对象。

    标记-整理算法

    对标记-清除算法的改进

    标记过程与标记-清除算法一样,但是标记完成后,存活对象向一端移动,然后清理边界的内存

    复制算法

    将内存分成两块容量大小相等的区域,每次只使用其中一块,当这一块内存用完了,就将所有存活对象复制到另一块内存空间,然后清除前一块内存空间。

    此种方法实现简单、效率较高,优点:

    1、不会产生内存碎;

    2、没有了先标记再删除的步骤,而是通过Tracing从 From内存中找到存活对象,复制到另一块To内存区域,From只要移动堆顶指针便可再次使用。

    缺点:

    1、复制的代价较高,所有适合新生代,因为新生代的对象存活率较低,需要复制的对象较少;

    2、需要双倍的内存空间,而且总是有一块内存空闲,浪费空间。

    分代收集算法

    分代收集算法主要将对象存储器的长短将内存进行划分。广泛地说,JVM堆内存被分为两部分:年轻代(Young Generator)和老年代(Old Generation)。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成。而老年代中因为对象存活率高、没有额外空间对他进行分配担保,就必须使用“标记-清理”或者“标记-整理”算法来进行回收。

    常用的垃圾收集器

    串行收集器(Serial Collector)

    (1)串行垃圾回收器在进行垃圾回收时,它会持有所有应用程序的线程,冻结所有应用程序线程,使用单个垃圾回收线程来进行垃圾回收工作。串行垃圾回收器是为单线程环境而设计的,如果你的程序不需要多线程,启动串行垃圾回收。

    (2)串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会STW(Stop The World)

    ParNew收集器

    ParNew收集器其实就是Serial收集器的多线程版本。新生代并行,老年代串行;

    Parallel Scavenge收集器

    Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例

    Parallel Old 收集器

    Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。

    CMS(Concurrent Mark Sweep)

    CMS收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器是基于“标记-清除”算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为4个步骤,包括:

    初始标记(CMS initial mark)

    并发标记(CMS concurrent mark)

    重新标记(CMS remark)

    并发清除(CMS concurrent sweep)

    其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。

    由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行。

    优点:并发收集、停顿时间短

     缺点:产生大量空间碎片

    G1收集器

    G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。与CMS收集器相比G1收集器有以下特点:

    (1). 空间整合,G1收集器采用标记整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次GC。

    (2). 可预测停顿,这是G1的另一大优势,降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。

    上面提到的垃圾收集器,收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合。

    G1的新生代收集跟ParNew类似,当新生代占用达到一定比例的时候,开始出发收集。和CMS类似,G1收集器收集老年代对象会有短暂停顿。

    收集步骤:

    1)、标记阶段,首先初始标记(Initial-Mark),这个阶段是停顿的(Stop the World Event),并且会触发一次普通Mintor GC。对应GC log:GC pause (young) (inital-mark)

    2)、Root Region Scanning,程序运行过程中会回收survivor区(存活到老年代),这一过程必须在young GC之前完成。

    3)、Concurrent Marking,在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收(图中打X)。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。

    4)、Remark, 再标记,会有短暂停顿(STW)。再标记阶段是用来收集 并发标记阶段 产生新的垃圾(并发阶段和应用程序一同运行);G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。

    5)、Copy/Clean up,多线程清除失活对象,会有STW。G1将回收区域的存活对象拷贝到新区域,清除Remember Sets,并发清空回收区域并把它返回到空闲区域链表中。

    6)、复制/清除过程后。回收区域的活性对象已经被集中回收到深蓝色和深绿色区域。

    唯一和串行垃圾回收器不同的是,并行垃圾回收器是使用多线程来进行垃圾回收工作的。

    相关文章

      网友评论

          本文标题:Java GC

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