JVM2-垃圾回收

1 垃圾回收-概述

1.1 如何判断对象为垃圾对象

  • 引用计数法
  • 可达行分析法

1.2 如何回收垃圾

  • 回收策略
    • 标记-清除算法
    • 复制算法
    • 标记-整理算法
    • 分代收集算法
  • 垃圾回收器
    • Serial
    • Parnew
    • Cms
    • G1

1.3 何时回收

2 垃圾回收-判断对象是否存活算法

  • 打印垃圾回收日志
  • -verbose:gc -xx:+PrintGCDetail (Run Configurations->VM arguments)

2.1 引用计数法详解

  • 在对象中添加一个引用计数器,当有地方引用这个对象的时候,引用计数器的值就+1,

当引用失效的时候,计数器的值就-1.

  • 当前回收器基本不用这种算法

堆内部相互引用的时候(栈指向堆对象1,堆对象1指向对象2,堆对象2指向对象3,如果对象1不被栈指向[栈->对象1为0],但是此时对象1->对象2,对象2->3 的引用不为0,而引用计数算法此时不会回收对象2,对象3)

2.2 可达性分析法详解

  • 通过一些被称为GCRoot的对象作为起始点,从这些节点开始往下搜索,搜索走过的路径称为引用链(Reference Chain),当一个对象到没有任何引用链相连时,则此对象是不可用的.(p64) [即:GCRoot不能到达(没用引用链)就算垃圾]

  • GCRoot的对象有:

    • 虚拟机栈(栈帧中的本地变量表)
    • 方法区中类静态属性所引用的对象
    • 方法区中常量所引用的对象
    • 本地方法栈中JNI(即Native)引用的对象

GC Root

2.3 再谈引用

2.3.1 强引用

以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空 间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

String str = "abc";
List<String> list = new Arraylist<String>();
list.add(str);
      
在list集合里的数据不会释放,即使内存不足也不会
复制代码

2.3.2 软引用

如果一个对象只具有软引用,那就类似于可有可物的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。

public class Test {  

    public static void main(String[] args){  
        System.out.println("开始");            
        A a = new A();            
        SoftReference<A> sr = new SoftReference<A>(a);  
        a = null;  
        if(sr!=null){  
            a = sr.get();  
        }  
        else{  
            a = new A();  
            sr = new SoftReference<A>(a);  
        }            
        System.out.println("结束");     
    }       

}  

class A{  
    int[] a ;  
    public A(){  
        a = new int[100000000];  
    }  
}  
复制代码

当内存足够大时可以把数组存入软引用,取数据时就可从内存里取数据,提高运行效率

  • 使用场景

    软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。

2.3.3 弱引用

如果一个对象只具有弱引用,那就类似于可有可物的生活用品。
复制代码
  • 弱引用与软引用的区别

只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
如:

Object c = new Car(); //只要c还指向car object, car object就不会被回收
WeakReference<Car> weakCar = new WeakReference(Car)(car);

当要获得weak reference引用的object时, 首先需要判断它是否已经被回收:

weakCar.get();
复制代码

import java.lang.ref.WeakReference;

public class TestWeakReference {
    public static void main(String[] args) {

        Car car = new Car(22000, "silver");
        WeakReference<Car> weakCar = new WeakReference<Car>(car);

        int i = 0;

        while (true) {
            if (weakCar.get() != null) {
                i++;
                System.out.println("Object is alive for " + i + " loops - " + weakCar);
            } else {
                System.out.println("Object has been collected.");
                break;
            }
        }
    }
}


class Car {
    private double price;
    private String colour;

    public Car(double price, String colour) {
        this.price = price;
        this.colour = colour;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public String getColour() {
        return colour;
    }

    public void setColour(String colour) {
        this.colour = colour;
    }

    public String toString() {
        return colour + "car costs $" + price;
    }

}
复制代码

在上例中, 程序运行一段时间后, 程序打印出”Object has been collected.” 说明, weak reference指向的对象的被回收了.

如果要想打出的是
Object is alive for “+i+” loops – “+weakCar

那么只要在这句话前面加上
System.out.println(“car==== “+car);
因为在此强引用了car对象

弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null。弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器标记。

2.3.4 虚引用

  • 虚引用顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。

  • 虚引用与软引用和弱引用的一个区别

虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是 否已经加入了虚引用,来了解 被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
特别注意,在世纪程序设计中一般很少使用弱引用与虚引用,使用软用的情况较多,这是因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。

  • 虚引用主要用来跟踪对象被垃圾回收的活动。
    虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。
    虚引用主要用于检测对象是否已经从内存中删除。

2.3.5 总结

  • 强引用:

String str = “abc”;
list.add(str);

  • 软引用:

如果弱引用对象回收完之后,内存还是报警,继续回收软引用对象

  • 弱引用:

如果虚引用对象回收完之后,内存还是报警,继续回收弱引用对象

  • 虚引用:

虚拟机的内存不够使用,开始报警,这时候垃圾回收机制开始执行System.gc(); String s = “abc”;如果没有对象回收了, 就回收没虚引用的对象

Java四种引用包括强引用,软引用,弱引用,虚引用
Java 关于强引用,软引用,弱引用和虚引用的区别与用法

3 垃圾回收算法

3.1 标记清除算法

3.1.1 概述

  • 算法分为”标记”和”清除”两个阶段:首先标记出需要回收的对象,在标记完成后统一回收所有被标记的对象. (p69)

根据一般是2.1的可达性分析法 标记出来的垃圾.进行回收处理.

  • 两个阶段。
  1. 标记阶段:找到所有可访问的对象,做个标记
  2. 清除阶段:遍历堆,把未被标记的对象回收

3.1.2 使用场景

该算法一般应用于老年代,因为老年代的对象生命周期比较长。

3.1.2 优缺点

3.1.2.1 优点

  • 是可以解决循环引用的问题
  • 必要时才回收(内存不足时)

3.1.2.2 缺点

  • 回收时,应用需要挂起,也就是stop the world。
  • 效率问题 标记和清除的效率不高,尤其是要扫描的对象比较多的时候
  • 空间问题 会造成不连续的内存碎片(会导致明明有内存空间,但是由于不连续,申请稍微大一些的对象无法做到),
    空间碎片多了之后会导致以后分配大对象时,无法找到连续空间而不得不提前出发另一次垃圾收集.

java垃圾回收算法之-标记清除

3.2 复制算法

3.2.1 概述

内存划分:

    • 新生代
      • Eden 伊甸园
      • Survivor1/2 存活区1/2
      • Tenured Gen 老年代-养老区
    • 老年代
  • 方法区(hotspot持久代)
    • 本地方法栈
    • 程序计数器

3.2.2 工作原理:

  • 两份内存区域,用其中一个,先标记使用的区域中的,然后将不被回收的复制到另一半空间(连续内存)
  • 划分Eden 80%、Survivor1 10%、Survivor 10%;内存对象在Eden中创建,如果不够用Survivor1,回收的时候将存活的移到Survivor2;然后再新对象在Eden和Survivor2中创建,然后再次回收的时候将存活的移到Survivor1中,如果往复;这样就只浪费10%的空间不可用;如果多次之后存货超过10%(一个Survivor的10%存不下了);则此时担保则放到Tenured Gen(老年代)中(内存担保[空间分配担保]).

3.2.3 使用场景

主要针对新生代,老年代不能直接选择这种(存活率高)

3.2.3 优缺点

  • 优点:提高效率并存活内存是连续的.
  • 缺点:造成一般空间的浪费,当存活对象率较高时进行较多的复制操作时效率变低.

3.3 标记整理算法

3.3.1 概述

  • 标记-整理-清除

先标记存活对象和垃圾对象, 整理后将存活对象移动到一端,让后清除掉端边界外的对象内存.

3.3.2 使用场景

**针对老年代 **

3.4 分代收集算法

  • 根据区域不同的收集算法
    • 新生代,内存回收率高的选择复制算法
    • 老年代,内存回收率低的选择标记整理算法

4. 垃圾收集器

4.1 serial收集器

4.1.1 概述

p76
垃圾收集器-Serial

  • serial/’sɪrɪəl//连续的/ serial收集器(串行垃圾收集器)
  • 复制算法
  • 优缺点: 单线程垃圾收集器,发展最早,单线程没有线程开销,效率高;

4.1.2 使用场景

应用桌面程序,Client模式的默认新生代收集器(客服端模式分配的JVM内存小,收集快,停顿无感知).

4.1.3 配置参数

  • -XX:+UseSerialGC 开启Serial收集器
  • Xms30m -Xmx30m -Xmn10m : Xms30m -Xmx30m 指定了JVM固定大小为30M,-Xmn10m 指JAVA新生代的空间为10M。

4.2 parnew收集器

p77
垃圾收集器-ParNew

  • 特征:并行进行,其实就是Serial的多线程版本.和Serial公用了很多代码 .
  • 使用场景: Server模式下虚拟机首先新生代收集器
  • 能和CMS收集器(老年代)配合使用
  • XX:+USeParNewGC 打开并发标记扫描垃圾回收器

4.3 parallel收集器(Parallel Scavenge)

4.3.1 概述

  • parallel /’pærəlɛl//平行的/
  • 复制算法
  • 多线程收集器
  • 达到可控制的吞吐量,所以又被称为 “吞吐量优先” 收集器

吞吐量:CPU用于运行用户代码的时间 与 CPU消耗的总时间的比值

吞吐量 = (执行用户代码时间) / (执行用户代码的时间 + 垃圾回收所占用的时间)

4.3.2 使用场景

  • 新生代收集器
  • 停顿时间越短,用户使用体验好.
  • 适合服务端:高可用,高并发,需要高的吞吐量.

4.3.3 配置参数

  • -XX:MaxGCPauseMillis 垃圾收集器最大停顿时间,单位1毫秒,如果设置过小,则新生代内存会变小,垃圾回收频率增大.所以需要根据实际场景来设定.
  • -XX:GCTimeRatio 吞吐量大小;(0,100) 开区间,不包含0和100;默认最大值:99(回收时间占1%)
  • -XX:+UseAdaptiveSizePolicy 开关参数,打开后就不用手工指定新生代大小(-Xmn),Eden与Survivor比例(-XX:SurvivorRatio),晋升老年代对象的大小(-XX:PretenureSizeThreshold)等参数细节,虚拟机会根据当前系统运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大吞吐量;这种方式被称为GC自适应的调节策略(GC Ergonomics);用该设置需要设置好最大堆(-Xmx)和MaxGCPauseMillis或GCTimeRatio.

4.4 cms收集器详解

4.4.1 概述

并发收集器 CMS(Concurrent Mark-Sweep)

  • Concurrent Mark Sweep(并发标记清除收集器)
  • 以获取最短回收停顿时间为目标的收集器.适用互联网站或B/S服务端
  • cms用作老年时,只能用Serial或ParNew新生代收集器.
  • 标记清除算法
  • 并发进行;减少延迟,提高速度
  • 并行与并发区别
并发(concurrency)和并行(parallellism)是:

解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
解释三:在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群
 
复制代码

并发是指一个处理器同时处理多个任务。
并行是指多个处理器或者是多核的处理器同时处理多个不同的任务。
并发是逻辑上的同时发生(simultaneous),而并行是物理上的同时发生。
来个比喻:并发是一个人同时吃三个馒头,而并行是三个人同时吃三个馒头

高并发:实现一个CPU处理多线程,而不是多个CPU处理多线程.

并发与并行的区别

4.4.2 工作过程

垃圾收集器-CMS

  • 初始标记(STW initial mark) 暂停应用

在这个阶段,需要虚拟机停顿正在执行的任务,官方的叫法STW(Stop The Word)。这个过程从垃圾回收的”根对象”开始,只扫描到能够和”根对象”直接关联的对象,并作标记。所以这个过程虽然暂停 了整个JVM,但是很快就完成了。

  • 并发标记(Concurrent marking)

这个阶段紧随初始标记阶段,在初始标记的基础上继续向下追溯标记。并发标记阶段,应用程序的线程和并发标记的线程并发执行,所以用户不会感受到停顿。

  • 并发预清理(Concurrent precleaning)

并发预清理阶段仍然是并发的。在这个阶段,虚拟机查找在执行并发标记阶段新进入老年代的对象(可能会有一些对象从新生代晋升到老年代, 或者有一些对象被分配到老年代)。通过重新扫描,减少下一个阶段”重新标记”的工作,因为下一个阶段会Stop The World。

  • 重新标记(STW remark) 暂停应用

重新标记 :这个阶段会暂停虚拟机,收集器线程扫描在CMS堆中剩余的对象。扫描从”跟对象”开始向下追溯,并处理对象关联。

  • 并发清理(Concurrent sweeping)

清理垃圾对象,这个阶段收集器线程和应用程序线程并发执行。

  • 并发重置(Concurrent reset)

这个阶段,重置CMS收集器的数据结构,等待下一次垃圾回收。

4.4.3 优缺点

4.4.3.1 优缺点
  • 并发收集
  • 低停顿
4.4.3.1 缺点
  • 占用大量的CPU资源(并行收集器都占用cpu).

具体原因如下:
CMS 对 CPU 资源敏感,在并发阶段虽然不会停止用户线程,但是会占用一部分线程来进行垃圾回收,总吞吐量会降低。CMS 默认启动的线程是(CPU数量+3)/4,当 CPU 大于4个以上占用资源不超过 25% 的 CPU 资源,但是小于 4 个 CPU 时候 CMS 收集器对用户程序的影响就比较大

  • 无法处理浮动垃圾
    边产生垃圾边打扫,打扫完后又扔垃圾,那就要下次来打扫了.

    CMS 在并发清理阶段还可以运行用户线程,这时候还会产生新的垃圾,而这部分垃圾CMS无法在本次回收掉,这部分就是浮动垃圾。因此CMS不能像其他的收集器等到年老代几乎全部满了再进行回收,需要预留一部分空间提供并发收集时候的用户线程使用。默认设置下,CMS 收集器在年老代使用了 68% 的空间后就会被激活,可以通过 -XX:CMSInitiatingOccupancyFraction 参数来设置这个属性.

  • 出现Concurrent Mode Failure
    留出一块内存区域用来清理过程中的新产生的对象, 这块空间如果过大就浪费,过小就会出现该错误(Concurrent Mode Failure).

    如果 CMS 在运行时候预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时候虚拟机临时启用 Serial Old 收集器重新来进行老年代的垃圾收集。 CMS是基于标记整理算法,在清理的过程中会有大量的空间碎片。空间碎片过多后给打对象分配空间会有很多麻烦。CMS 提供了一个参数 -XX:+UseCMSCompactAtFullCollection 用来在 Full GC 完成后附加一个碎片整理过程,碎片整理无法并发会导致停顿时间变长。当然还提供了一个参数-XX:CMSFullGCsBeforeCompaction,这个参数设置在执行多少次不压缩的 Full GC 后,跟着来一次带压缩的。

  • 空间碎片 CMS不会整理、压缩堆空间

是因为使用标记清除算法引起的.

4.4.4 常用配置

  • -XX:+UseConcMarkSweepGC 开启CMS设置
  • -XX:CMSInitiatingOccupancyFraction 设置老年代使用数目来激活CMS.
  • -XX:+UseCMSCompactAtFullCollection 在Full GC 完成后附加一个碎片整理过程
  • -XX:CMSFullGCsBeforeCompaction 执行多少次不压缩的 Full GC 后,跟着来一次带压缩的

4.5 g1收集器

4.5.1 基础概念

  • Stop-The-World(STW)
    是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)。Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互;这些现象多半是由于gc引起,暂停所有当前运行的线程

  • 安全点
    当所有线程都跑到了安全点,或者进入安全区域之后,才会进行GC
    在安全点,虚拟机会生成OopMap用来记录引用关系(这也是不能在任何地方停下的原因,如果每一条指令都生成OopMap那么效率会非常低,也会占用大量的空间)

    一般安全点设置在以下位置:
    •方法调用 (方法临返回前 / 调用方法的call指令后)
    •循环跳转 (循环的末尾 )
    •异常跳转 (可能抛异常的位置)

    那么JVM是如何让线程停下的呢?事先会约定一个标志,当需要进行GC的时候,JVM会更改这个标志的值,线程在运行的时候会轮询这个标志,当收到要发生GC信号,他会运行到下一个安全点停下来,等待GC的进行

    当然,仅仅用安全点是不够的,有下面一种情况,就是当线程sleep或者阻塞的时候,他根本就不会运行,更谈不上进入安全点了,更不可能让所有的线程去等它,于是引入了安全区域这个概念

Java系列:JVM中的OopMap

  • 安全区域

    当线程进入安全区域,如sleep或者阻塞时,会标志自己已经进入了安全区域,当进行GC的时候,就不用去管它了,当他要离开安全区域是,会先看看JVM已经完成了GC没有,如果没有就等到GC完成之后再离开安全区域

  • 触发安全点的时刻

    除了GC,其他触发安全点的VM Operation包括:

    1. JIT相关,比如Code deoptimization, Flushing code cache
    2. Class redefinition (e.g. javaagent,AOP代码植入的产生的instrumentation)
    3. Biased lock revocation 取消偏向锁
    4. Various debug operation (e.g. thread dump or deadlock check)
  • JVM 性能关心的指标有

    • Throughput: CPU不花在垃圾回收时间上的比例。包含内存分配时间。
    • Pauses: 因垃圾回收而造成无法响应的时间
    • Footprint: 程序所用的内存(is the working set of a process, measured in pages and cache lines)
    • Promptness: 对象死掉(没有其他对象引用时,unreachable时)到内存可用时(gc后才可用嘛)的时间差,(is the time between when an object becomes dead and when the memory becomes available, an important consideration for distributed systems, including remote method invocation (RMI))

4.5.2 概述

  • g1 = garbage first ,垃圾回收优先第一. 最牛的垃圾收集器
  • 面向服务端,G1的设计原则就是简单可行的性能调优
  • 并发、并行 充分利用多CPU、多核,缩短Stop-The-World停顿时间。
  • 分代(增量式)收集 不分老年代,新生代之类,分为内存区域(region),然后根据内存区域回收.内存区域有n个Eden、surviror、old和Humongous
  • 算法: ”标记-整理“(CMS是”标记-清理“),两个Region之间又是”复制“。 空间整合 碎片整理在一起.
    • G1从整体上看是基于标记整理算法,从局部(两个Region之间)看是基于复制算法实现,这两种算法都不会产生内存碎片
  • 可预测的停顿 停顿处理比cms强大很多,可指定长度为M毫秒的时间片段内,消耗在垃圾收集器上的时间不超过N毫秒. Stop The World.可预测的原因是可以有计划的避免在整个java堆中进行全区域的垃圾收集.
  • Remember Set来记录对象的引用, 来对每个Region来打分.然后整理.

4.5.3 优势

     Hotspot之前已经携带了Serial, Paralel, CMS等收集器,为什么还需要研发一个新的G1呢?
垃圾收集的三个性能指标: footprint, max pause time,throughput似乎像CAP一样不能同时满足。 (Heap越大暂停时间越长)

    在服务端更注重的是短停顿时间,也就是stop-the-world的时间,另外一段时间内的总停顿时间也是一个衡量指标。

    Mark-Sweep, Mark-Compact均需要和清理区域大小成比例的工作量,而Copying算法则需要一般是一半的空间用于存放每次copy的活对象。CMS的Initial Marking和Remarking两个STW阶段在Heap区越来越大的情况下需要的时间越长,并且由于内存碎片,需要压缩的话也会造成较长停顿时间。所以需要一种高吞吐量的短暂停时间的收集器,而不管堆内存多大。那就是G1了.

4.5.4 工作原理

G1-Forget
G1-Region

图片中E指Eden, S是Survivor, H指Humongous, O是Old, 空白区域是可用分区。

  • Region: 化整为零的思想.

G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器。Region中有Eden,Survivor,Old,Humongous各种类型,(不需要连续) .

G1收集器将堆内存划分为一系列大小相等的Region区域,Region大小在1MB到32MB在启动时确定,G1同样也使用分代收集策略,将堆分为Eden, Survivior,Old等,只不过是按照逻辑划分的,每个Region逻辑上属于一个分代区域,并且在物理上不连续,当一个Old的Region收集完成后会变成新可用Region并可能成为下一个Eden Region。
当申请的对象大于Region大小的一半时,会被放入一个Humongous Region(巨型区域)中。当一个Region中是空的时,称为可用Region或新Region。

    • 新生代:

这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。

    • 老年代:

老年代也分成很多区域,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。

    • Humongous: /hjuˈmɑŋɡəs//极大的/

如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象,这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响,为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。

  • G1年轻代收集器是并行Stop-the-world收集器,和其他的HotSpot GC一样,当一个年轻代GC发生时,整个年轻代被回收。G1的老年代收集器有所不同,它在老年代不需要整个老年代回收,只有一部分Region被调用.

  • 当一个Java堆空间瓶颈点超过后,即堆空间耗尽,这时G1初始化老年代收集器,这个Initial-Mark阶段是一个并行Stop-the-World的,它的大小和老年代以及整个Java堆大小有关。

    • 持久代也移动到了普通的堆内存空间中,改为元空间
  • 垃圾堆积价值

    G1跟踪各个Region里面的垃圾堆积价值大小.(回收所获空间与回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也是Garbage-First名称的由来),这样可以提高回收垃圾的效率。

  • Remembered Set

    Region除了可能被本区域的对象引用外,还可能被其它Region的对像引用.新生代Region和老年代Region也可相互引用,引出Remembered Set避免全堆扫描. 每个Region都有一个对应的Remembered Set ,虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处在不同的Region之中(在分代的例子中就是检查是否老年代中的对象引用新生代中的对象),如果是,便通过CardTable把**相关引用信息记录到被引用对象所属的Region的Remembered Set(被引用的记录被谁引用)**之中.当进行内存回收时,在GC根节点的枚举范围内中加入Remebered Set即可保证不对全堆扫描也不会有遗漏.

只有来自其他Region的引用需要记录在RS中,所以Region内部的引用和null都不需要记录RS。

  • CardTable CardTable是一种remembered set

因为G1只回收一部分Region, 所以回收的时候需要知道哪些其他Region的对象引用着自己Region的对象,因为采用的copying算法需要移动对象,所以要更新引用为对象的新地址,在普通的分代收集中也是如此,分代收集中年轻代收集需要老年代到年轻代的引用的记录,通常叫做remembered set(简称RS)。CardTable是一种remembered set, 一个card代表一个范围的内存,目前采用512bytes表示一个card,cardtable就是一个byte数组,每个Region有自己的cardtable。维护remembered set需要mutator线程在可能修改跨Region的引用的时候通知collector, 这种方式通常叫做write barrier(和GC中的Memory Barrier不同), 每个线程都会有自己的remembered set log,相当于各自的修改的card的缓冲buffer,除此之外还有全局的buffer, mutator自己的remember set buffer满了之后会放入到全局buffer中,然后创建一个新的buffer。

  • 增量收集(incremental collection)

Region避免长暂停时间,可以考虑将堆分成多个部分,一次收集其中一部分,这样的方式又叫做增量收集(incremental collection), 分代收集也可以看成一种特殊的增量收集。

  • SATB

全称是Snapshot-At-The-Beginning,由字面理解,是GC开始时活着的对象的一个快照。它是通过Root Tracing得到的,作用是维持并发GC的正确性。
那么它是怎么维持并发GC的正确性的呢?根据三色标记算法,我们知道对象存在三种状态:
•白:对象没有被标记到,标记阶段结束后,会被当做垃圾回收掉。
•灰:对象被标记了,但是它的field还没有被标记或标记完。
•黑:对象被标记了,且它的所有field也被标记完了。

4.5.5 对象分配策略

说起大对象的分配,我们不得不谈谈对象的分配策略。它分为3个阶段:

1)TLAB(Thread Local Allocation Buffer)线程本地分配缓冲区
2)Eden区中分配
3)Humongous区分配

TLAB为线程本地分配缓冲区,它的目的为了使对象尽可能快的分配出来。如果对象在一个共享的空间中分配,我们需要采用一些同步机制来管理这些空间内的空闲空间指针。在Eden空间中,每一个线程都有一个固定的分区用于分配对象,即一个TLAB。分配对象时,线程之间不再需要进行任何的同步。

对TLAB空间中无法分配的对象,JVM会尝试在Eden空间中进行分配。如果Eden空间无法容纳该对象,就只能在老年代中进行分配空间。

4.5.6 工作过程

G1-Process

初始标记==>根区域扫描==>并发标记==>最终标记==>筛选回收

4.5.6.1 工作过程概述

  • 初始标记(initial mark,STW)

在此阶段,G1 GC 对根GCRoot进行标记。该阶段与常规的 (STW) 年轻代垃圾回收密切相关。
p86: 初始标记仅仅只是标记一下GC Root能直接关联到的对象并且修改TAMS(Next Top Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这阶段需要停顿线程,但耗时很短.

  • 根区域扫描(root region scan)

G1 GC 在初始标记的存活区扫描对老年代的引用,并标记被引用的对象。该阶段与应用程序(非 STW)同时运行,并且只有完成该阶段后,才能开始下一次 STW 年轻代垃圾回收。

  • 并发标记(Concurrent Marking)

G1 GC 在整个堆中查找可访问的(存活的)对象。该阶段与应用程序同时运行,可以被 STW 年轻代垃圾回收中断
p86:并发标记阶段是从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行.

在不产生stop-the-world,与程序进程并发的情况下,活跃度(可达性分析)被分析出来。
活跃度越低,代表回收的效率越高,越值得优先回收。

  • 最终标记(Remark,STW)

该阶段是 STW 回收,帮助完成标记周期。G1 GC 清空 SATB 缓冲区,跟踪未被访问的存活对象,并执行引用处理。
p86:最终标记则是为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面. 最终标记阶段需要吧Remembered Set Logs的数据合并到Remembered Set中. 这阶段需要停顿线程,但是可并行执行.

  • 筛选回收(Cleanup,STW)

在这个最后阶段,G1 GC 执行统计和 RSet 净化的 STW 操作。在统计期间,G1 GC 会识别完全空闲的区域和可供进行混合垃圾回收的区域。清理阶段在将空白区域重置并返回到空闲列表时为部分并发
p86:筛选阶段回收首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,这阶段其实也可以做到与用户程序并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率.

年轻代、老年代在这个阶段同时被回收掉。老年代被回收的region,是根据这个region的存活度来选择的。

4.5.6.2 工作过程详细

  • Marking(标记)

G1收集器的标记阶段负责标记处存活的对象、并且计算各个Region的活跃度等。

G1使用了一种Snaphot-At-The-Beginning简称SATB的标记算法, 记录标记开始时的对象图的快照,之后并发收集过程中的新申请的对象都认为是存活对象, 当堆使用比例超过InitiatingHeapOccupancyPercent后开始marking阶段,使用SATB记录marking开始阶段的对象图快照,。

G1使用bitmap标记处哪些位置已经完成标记了,一个bitmap的bit表示8bytes, 我们使用两个marking bitmap,一个previous、一个next, previous marking bitmap表示已经完成标记的部分,标记完成后会交换previous和next

标记阶段分为几个步骤:

    • Initial Marking Phase

标记周期的最开始是清除next marking bitmap,是并发执行的。然后开始initial marking phase, 会暂停所有线程,标记出所有可以直接从GC roots可以到达的对象,这是在Young GC的暂停收集阶段顺带进行的。

    • Root Region Scan Phase

这个阶段G1扫描Initial Marking阶段标记的Survivor Region,

    • Concurrent Marking Phase

这个阶段G1通过tracing找出整个堆所有的可到达的对象。这个阶段是并发执行的。

    • Remark Phase

Remark是一个STW阶段,G1将所有的SATB buffer处理完成。

    • Cleanup Phase

marking的最后一个阶段,G1统计各个Region的活跃性,完全没有存活对象的Region直接放入空闲可用Region列表中,然后会找出mixed GC的Region候选列表。

  • 收集过程

和一般的分代式收集不同,G1中除了普通的Young GC,还有Mixed GC。

Young Garbage Collection
当Eden区域无法申请新的对象时(满了),就会进行Young GC, Young GC将Eden和Survivor区域的一些Region(称为Collection Set, CSet)中的活对象Copy到一些新Region中(即新的Survivor),当对象的GC年龄达到阈值后会Copy到Old Region中。由于采取的是Copying算法,所以就避免了内存碎片的问题,不再需要单独的压缩。

Mixed Garbage Collection
当整个Heap的对象占总Heap的比例超过InitiatingHeapOccupancyPercent之后,就会开始ConcurentMarking, 完成了Concurrent Marking后,G1会从Young GC切换到Mixed GC, 在Mixed GC中,G1可以增加若干个Old区域的Region到CSet中,当G1回收了足够的内存后又会回退到Young GC。

Full GC
和CMS一样,G1的一些收集过程是和应用程序并发执行的,所以可能还没有回收完成,是由于申请内存的速度比回收速度快,新的对象就占满了所有空间,在CMS中叫做Concurrent Mode Failure, 在G1中称为Allocation Failure,也会降级为一个STW的fullgc。

Floating Garbage
G1使用一种Snapshot-At-The-Begining的方式记录活对象,也就是那一时刻(整个堆concurrent marking开始的时候)的内存的Object graph, 但是在之后这里面的对象可能会变成Garbage, 叫做floating garbage 只能等到下一次收集回收掉。

4.5.7 常用配置

  • –XX:+UseG1GC 使用G1垃圾回收器
  • -XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=200
  • -XX:+PrintGCApplicationStoppedTime GC日志中打印”stop-the-world”(STW)暂停时间
  • -XX:+PrintSafepointStatistics -XX: PrintSafepointStatisticsCount=1 打印时间戳,和发生进入安全点的原因(VM Operation的类型),以及线程概况

神奇的G1——Java全新垃圾回收机制

4.6 收集器总结对比

4.6.1 CMS和G1对比

4.6.1.1 工作过程

垃圾收集器-CMS
CMS工作原理

G1-Process

CMS:初始标记->并发标记->并发预清理->重新标记->并发清理->并发重置

G1: 初始标记->并发标记->最终标记->筛选回收

•初始标记:标记出所有与根节点直接关联引用对象。需要STW
•并发标记:遍历之前标记到的关联节点,继续向下标记所有存活节点。 在此期间所有变化引用关系的对象,都会被记录在Remember Set Logs中
•最终标记:标记在并发标记期间,新产生的垃圾。需要STW
•筛选回收:根据用户指定的期望回收时间回收价值较大的对象(看”原理”第二条)。需要STW

  • 对比总结

•从上图来看,G1与CMS相比,仅在最后的”筛选回收”部分不同(CMS是并发清除),实际上G1回收器的整个堆内存的划分都与其他收集器不同。
•CMS需要配合ParNew,G1可单独回收整个空间

4.6.1.2 内存划分

CMS

    • 新生代
      • Eden 伊甸园
      • Survivor1/2 存活区1/2
      • Tenured Gen 老年代-养老区
    • 老年代

G1

  • Region
    • E指Eden,
    • S是Survivor,
    • H指Humongous,
    • O是Old,
    • 空白区域是可用分区。

4.6.1.3 优缺点及特性

CMS(ConcurrentMarkSweep):

  • 获取最短回收停顿时间为目标的收集器
  • 特性:分代:新生代(),老年代
  • 算法:标记-清除
  • 优点: 并发收集,低停顿
  • 缺点: 空间碎片多,如果开启整理会造成停顿时间变长,
  • 缺点: 预留空间不足会造成:Concurrent Mode Failure

G1(garbage first):

  • 垃圾回收优先第一,缩短Stop-The-World停顿时间为目标

  • 特性: 可预测的停顿(指定一个长度为 M 毫秒的时间片段内,消耗在垃圾回收的时间不超过 N 毫秒。)
    原因:根据”垃圾堆积价值”回收,引入Remember Set来记录对象的引用, 来对每个Region来打分.

  • 特性:不分代,引进Region(不需要连续),只保留分代概念;G1 可以不需要其他收集器配合就可以独立管理整个GC堆

  • 算法: 整体上标记整理算法, 从局部(两个Region之间)看是基于复制算法实现,这两种算法都不会产生内存碎片。

  • 优点:并行与并发:G1 可以充分利用多 CPU

Jvm 垃圾收集器 CMS & G1
垃圾收集器Serial 、Parallel、CMS、G1

4.7 小总结

4.7.1 基本概念总结

  • 两个最基本的java回收算法:复制算法和标记清理算法
    • 复制算法:两个区域A和B,初始对象在A,继续存活的对象被转移到B。此为新生代最常用的算法
    • 标记清理:一块区域,标记要回收的对象,然后回收,一定会出现碎片,那么引出
    • 标记-整理算法:多了碎片整理,整理出更大的内存放更大的对象
  • 两个概念:新生代和年老代
    • 新生代:初始对象,生命周期短的
      • Eden 伊甸园
      • Survivor1/2 存活区1/2
      • Tenured Gen 老年代-养老区
    • 老年代 长时间存在的对象

整个java的垃圾回收是新生代和年老代的协作,这种叫做分代回收。

4.7.2 收集器对比

  • Serial New收集器是针对新生代的收集器,采用的是复制算法
    一个单线程的收集器,在进行垃圾收集时候,必须暂停其他所有的工作线程直到它收集结束。
    特点:CPU利用率最高,停顿时间即用户等待时间比较长。
    适用场景:小型应用
    通过JVM参数-XX:+UseSerialGC可以使用串行垃圾回收器。

  • Parallel New(并行)收集器,新生代采用复制算法,老年代采用标记整理
    采用多线程来通过扫描并压缩堆
    特点:停顿时间短,回收效率高,对吞吐量要求高。
    适用场景:大型应用,科学计算,大规模数据采集等。
    通过JVM参数 XX:+USeParNewGC 打开并发标记扫描垃圾回收器。

  • Parallel Scavenge(并行)收集器,针对新生代,采用复制收集算法

  • Serial Old(串行)收集器,新生代采用复制,老年代采用标记清理

  • Parallel Old(并行)收集器,针对老年代,标记整理

  • CMS收集器,基于标记清理
    特点:响应时间优先,减少垃圾收集停顿时间
    适应场景:服务器、电信领域等。
    通过JVM参数 -XX:+UseConcMarkSweepGC设置

  • G1收集器:整体上是基于标记清理,局部采用复制
    在G1中,堆被划分成 许多个连续的区域(region)。采用G1算法进行回收,吸收了CMS收集器特点。
    特点:支持很大的堆,高吞吐量
    –支持多CPU和垃圾回收线程
    –在主线程暂停的情况下,使用并行收集
    –在主线程运行的情况下,使用并发收集
    实时目标:可配置在N毫秒内最多只占用M毫秒的时间进行垃圾回收
    通过JVM参数 –XX:+UseG1GC 使用G1垃圾回收器

综上:新生代基本采用复制算法,老年代采用标记整理算法。cms采用标记清理。

  • 适用范围

追求STW短:若ParNew/CMS用的挺好,就用这个;若不符合,用G1
•追求吞吐量:用Parallel Scavenge/Parallel Old,而G1在吞吐量方面没有优势

4.7.3 常用收集器组合

新生代 老年代
Serial Serial Old
Serial CMS
ParNew CMS
ParNew Serial Old
Parallel Scavenge Serial Old
Parallel Scavenge Parallel Old
ParallelG1 G1

4.7.4 JDK默认垃圾回收器

jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.9 默认垃圾收集器G1

5.内存分配

5.1 内存分配-概述

内存分配策略

  • 优先分配到Eden
  • 大对象直接分配到老年代
  • 长期存活的对象分配到老年代
  • 空间分配担保

空间不足时到老年代去借空间

  • 动态对象年龄判断

5.2 内存分配-优先分配到Eden

一般情况下,对象会在新生代的Eden区分配,Eden区没有足够空间时,虚拟机会 发起一次MinorGC;当MinorGC时,若无法放入survivor空间,就会再通过分配担保机制转移到老年代中;

main(){
    byte [] b1 = new byte[4*1024*1024];
    
    //System.gc();
}
复制代码

5.3 内存分配-大对象直接进老年代

JVM设置如下:
-Xms20M 堆内存大小起步
-Xmx20M 堆内存大小最大
-Xmn10M 新生代大小
-XX:SurvivorRatio=8 (Eden80%:s1 10%,s2 10%)

main(){
    byte [] b1 = new byte[8*1024*1024];
}

会发现8M直接进入老年代

byte [] b1 = new byte[7*1024*1024];
7M还在新生代.


复制代码

-XX:PretenureSizeThreshold=6M
大于这个值就进入老年代

5.4 内存分配-长期存活的对象进入老年代

-XX:MaxTenuringThreshold 默认15
survivor中垃圾回收时(MinorGC)存活一次,加一次. 达到配置值就进入老年代.JDK6之前版本可行.
在从JDK,7,8开始,这个就没那么精确了.

在survivor中年龄相同的所有对象大小总和大于 PretenureSizeThreshold,参数的一半,年龄大于或等于该年龄的对象进入老年代

5.5 内存分配-空间分配担保

-XX:-HandlePromotionFailure
两步: 1.验证老年代不能容纳(全部)新生代的
2.老年代可用的空间是否大于历次从新生代进入老年代的平均大小. 如果可用空间小于平均大小则担保失败.

Eden放不下,Survivor也放不开,新生代放不开,则向有内存的借,内存分配担保.

JVM设置如下:
-Xms20M 堆内存大小起步
-Xmx20M 堆内存大小最大
-Xmn10M 新生代大小
-XX:SurvivorRatio=8 (Eden80%:s1 10%,s2 10%)

代码如下:
main{
    byte [] b1 = new byte[20*1024*1024];
    byte [] b2 = new byte[20*1024*1024];
    byte [] b3 = new byte[20*1024*1024];
    byte [] b4 = new byte[40*1024*1024];
}
解读:
b1Eden,b2Eden,b3Eden,b4来的时候Eden放不小,所以MinorGC准备S1,发现S1也放不小,则3个2M空间担保到老年代(tenred generation),然后b4又放到Eden中了.
复制代码

每次MinorGC之前,会检查老年代最大连续可用空间是否大于 新生代所有对象的总空间,

  • 1)如果大于则表示安全可进行MinorGC;
  • 2)如果小于则检查HandlePromotionFailure(是否允许担保失败)情况
    (JDK6U24不再检查HandlePromotionFailure,一定会冒险)
    • 2.1) HandlePromotionFailure为true:
      表示允许冒险,则本次晋升到老年代的对象与历次晋升到老年代的对象平均大小比较
      • 2.1.1) 若大于则进行MinorGC(本次晋升到老年代的对象>历次晋升到老年代的对象平均),
      • 2.1.2) 若小于则进行FullGC(本次晋升到老年代的对象<历次晋升到老年代的对象平均);
    • 2.2) HandlePromotionFailure
      进行FullGC;

5.6 内存分配-逃逸分析与栈上分配

5.6.1 逃逸分析

主要目标:分析对象作用域:

  • 如果一个方法体它的受访问权限,只限于方法体内, 一旦引用外部成员,这个对象就发生了逃逸(示例中的:StackAllocation.getInstance).

  • 如果对象只在方法体的内部有效,就认为这个对象没有发生逃逸,那么这个没有逃逸的可以分配到栈上去.

总结:不发生逃逸的对象内存分配到栈内存中去,方法结束,栈内存就移除,内存就会被回收. 使用的时候如果能用局部变量就不要用类对象.

public class StackAllocation {
    
        public StackAllocation obj;

        /**方法返回StackAllocation对象,发生逃逸**/
        public StackAllocation getInstance(){
            return obj==null?new StackAllocation():obj;
        }

        /**为成员变量属性赋值,发生逃逸**/
        public void setObj(){
            this.obj = new StackAllocation();
        }

        /***对象的作用域仅在当前方法中有效,没有发生逃逸*/
        public void userStackAllocation(){
            StackAllocation s = new StackAllocation();
        }

        /**引用成员变量的值,发生逃逸**/
        public void useStackAllocation2(){
            StackAllocation s = getInstance();
        }

}

复制代码

5.6.2 栈上分配

栈方法的进行和执行结束后来释放,这样效率高.

6.垃圾回收总结

6.0 回顾

程序计数器:线程私有。是一块较小的内存,是当前线程所执行的字节码的行号指示器。是Java虚拟机规范中唯一没有规定OOM(OutOfMemoryError)的区域。

Java栈:线程私有。生命周期和线程相同。是Java方法执行的内存模型。执行每个方法都会创建一个栈帧,用于存储局部变量和操作数(对象引用)。局部变量所需要的内存空间大小在编译期间完成分配。所以栈帧的大小不会改变。存在两种异常情况:若线程请求深度大于栈的深度,抛StackOverflowError。若栈在动态扩展时无法请求足够内存,抛OOM。

Java堆:所有线程共享。虚拟机启动时创建。存放对象实力和数组。所占内存最大。分为新生代(Young区),老年代(Old区)。新生代分Eden区,Servior区。Servior区又分为From space区和To Space区。Eden区和Servior区的内存比为8:1。 当扩展内存大于可用内存,抛OOM。

方法区:所有线程共享。用于存储已被虚拟机加载的类信息、常量、静态变量等数据。又称为非堆(Non – Heap)。方法区又称“永久代”。GC很少在这个区域进行,但不代表不会回收。这个区域回收目标主要是针对常量池的回收和对类型的卸载。当内存申请大于实际可用内存,抛OOM。

本地方法栈:线程私有。与Java栈类似,但是不是为Java方法(字节码)服务,而是为本地非Java方法服务。也会抛StackOverflowError和OOM。

6.1 GC简结

6.1.1 GC机制

要准确理解Java的垃圾回收机制,就要从:“什么时候”,“对什么东西”,“做了什么”三个方面来具体分析。

第一:“什么时候”即就是GC触发的条件。GC触发的条件有两种。

(1)程序调用System.gc时可以触发;
(2)系统自身来决定GC触发的时机。

系统判断GC触发的依据:根据Eden区和From Space区的内存大小来决定。当内存大小不足时,则会启动GC线程并停止应用线程。

第二:“对什么东西”笼统的认为是Java对象并没有错。但是准确来讲,GC操作的对象分为:通过可达性分析法无法搜索到的对象和可以搜索到的对象。对于搜索不到的方法进行标记。

第三:“做了什么”最浅显的理解为释放对象。但是从GC的底层机制可以看出,对于可以搜索到的对象进行复制操作,对于搜索不到的对象,调用finalize()方法进行释放。

6.1.2 对于用可达性分析法搜索不到的对象,GC并不一定会回收该对象

对于用可达性分析法搜索不到的对象,GC并不一定会回收该对象。要完全回收一个对象,至少需要经过两次标记的过程。

第一次标记:对于一个没有其他引用的对象,筛选该对象是否有必要执行finalize()方法,如果没有执行必要,则意味可直接回收。(筛选依据:是否复写或执行过finalize()方法;因为finalize方法只能被执行一次)。

第二次标记:如果被筛选判定位有必要执行,则会放入FQueue队列,并自动创建一个低优先级的finalize线程来执行释放操作。如果在一个对象释放前被其他对象引用,则该对象会被移除FQueue队列。

GC详解及Minor GC和Full GC触发条件总结

6.2 MinorGC\MajorGC\FullGC

6.2.1 MinorGC

  • 简介
    MinorGC 是清理整合YouGen年轻代空间(包括Eden和Survivor区域)的过程;

  • 触发条件

当Eden区满时,触发Minor GC。
当JVM 无法为一个新的对象分配空间(Allocation Failure)时会触发 Minor GC,比如当 Eden 区快满了

  • 特征

因大多数新生对象生命周期很短新生代MinorGc的频率高,时间短.
当 JVM 无法为一个新的对象分配空间时会触发 Minor GC。所以分配率越高,越频繁执行 Minor GC。
会触发stop-the-world

  • 工作原理

当GC线程启动时,会通过可达性分析法把Eden区和From Space区的存活对象复制到To Space区,然后把Eden Space和From Space区的对象释放掉。
当GC轮训扫描To Space区一定次数后,把依然存活的对象复制到老年代,然后释放To Space区的对象。

执行 Minor GC 操作时,不会影响到永久代。从永久代到年轻代的引用被当成 GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉

6.2.2 MajorGC

  • 简介

Major GC清理Tenured区,用于回收老年代,出现Major GC通常会出现至少一次Minor GC。

  • 触发条件
  • 工作原理

MajorGC:
清理整合OldGen的内存空间

6.2.3 FullGC

  • 简介

Full GC是针对整个新生代、老生代、元空间(metaspace,java8以上版本取代perm gen)的全局范围的GC。Full GC不等于Major GC,也不等于Minor GC+Major GC,发生Full GC需要看使用了什么垃圾收集器组合,才能解释是什么样的垃圾回收。

Full GC 是清理整个堆空间—包括年轻代和永久代。

  • 触发条件
    • System.gc,系统建议执行Full GC,但是不必然执行
    • 老年代空间不足
    • 方法区(持久代)空间不足
    • 通过Minor GC后进入老年代的平均大小大于老年代的可用内存(为了避免新生代晋升到老年代失败)

    当使用G1,CMS 时,FullGC发生的时候 是 Serial+SerialOld。
    当使用ParalOld时,FullGC发生的时候是 ParallNew +ParallOld.

    • 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
    • promotion failed (年代晋升失败,比如eden区的存活对象晋升到S区放不下,又尝试直接晋升到Old区又放不下,那么Promotion Failed,会触发FullGC)
    • CMS的Concurrent-Mode-Failure 由于CMS回收过程中主要分为四步:

(1).CMS initial mark (2).CMS Concurrent mark (3).CMS remark (4).CMS Concurrent sweep。

在第2步中gc线程与用户线程同时执行,那么用户线程依旧可能同时产生垃圾,如果这个垃圾较多无法放入预留的空间就会产生CMS-Mode-Failure, 切换为SerialOld单线程做mark-sweep-compact。

  • 特征

FullGC 频率低,时间长.

  • 工作原理

7.参考文章

Serial,Parallel,CMS,G1四大GC收集器特点小结
系列文章-G1与CMS对比
深入理解 Java G1 垃圾收集器
深入理解g1垃圾收集器
G1收集器和CSM收集器对比
JVM-系列文章之-JAVA Stop The World
JVM的Stop The World,安全点,黑暗的地底世界
jvm系列 (二) —垃圾收集器与内存分配策略
深入理解JVM 一GC(下) G1 Garbage Collector
Garbage First G1收集器 理解和原理分析
JVM G1详解
好的系列文章–第一部分-JVM虚拟机内存区域划分和堆中的对象信息
内存模型之堆内存(Heap)
深入理解GC ——MinorGC\MajorGC\FullGC

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享