天天看点

Java 垃圾回收机制(GC)

文章目录

      • 一、标记算法
        • 1、对象被判定为垃圾的标准
        • 2、判定对象是否为垃圾的算法
        • 3、引用计数算法
        • 4、可达性分析算法
      • 二、垃圾回收算法
        • 1、标记 - 清除算法(Mark and Sweep)
        • 2、复制算法(Copying)
        • 3、标记 - 整理算法(Compacting)
        • 4、分代收集算法(Generational Collector)
      • 三、新生代垃圾收集器
        • 1、Stop-the-World
        • 2、Safepoint
        • 3、JVM模式
        • 4、垃圾收集器之间的联系
        • 5、年轻代常见的垃圾收集器
      • 四、老年代常见的垃圾收集器
        • 1、Serial Old 收集器(-XX:+UseSerialOldGC,标记-整理算法)
        • 2、Parallel Old 收集器(-XX:+UseParallelOldGC,标记-整理算法)
        • 3、CMS收集器(-XX:UseConcMarkSweepGC,标记-清除算法)
        • 4、G1收集器(-XX:+UseG1GC,复制+标记-整理算法)
        • 5、JDK11新增的GC
        • 五、常见问题
        • 1、Object 的 finalize()方法的作用 是否 与C++的析构函数作用相同
        • 2、Java中的强引用,软引用,弱引用,虚引用有什么用

一、标记算法

1、对象被判定为垃圾的标准

​ 没有被其他对象引用。

2、判定对象是否为垃圾的算法

  1. 引用计数算法
  2. 可达性分析算法

3、引用计数算法

  • 判断对象的引用数量
  1. 通过判断对象的引用数量来决定对象是否可以被回收
  2. 每个对象实例都有一个引用计数器,被引用则 +1,完成引用则 -1
  3. 任何引用计数为0的对象实例,可以被当作垃圾收集
  • 优点

    执行效率高,程序执行受影响较小。

  • 缺点

    无法检测出循环引用的情况,导致内存泄露

public class MyObject {
    public MyObject ref;
}

public class ReferenceCounterProblem {
    public static void main(String[] args) {
        MyObject o1 = new MyObject();
        MyObject o2 = new MyObject();
        // 循环引用
        o1.ref = o2;  // o1 引用计数为 2
        o2.ref = o1;  // o2 引用计数为 2
        o1 = null;    // o1 引用计数为 1
        o2 = null;    // o2 引用计数为 1
        // 如果要使 o1 引用计数变为0,需将 o2.ref=null, 而要清除 02.ref 这个引用,需要其对象的引用计数也是0,而其对象的也是1,因为 o1.ref指向了它,从而陷入了死循环。
    }
}

           

4、可达性分析算法

​ 通过判断对象的引用链是否可达来决定对象是否可以被回收。

​ 从离散数学中的图论引入,程序把所有的引用关系看做一张图,通过一系列的 GC root 作为起始点开始向下搜索,搜索中走过的路径称为 引用链(Reference Chain),当一个对象没有与任何引用链相连,则该对象不可达,即会被标记为垃圾对象。

Java 垃圾回收机制(GC)
  • GC Root的对象
  1. 虚拟机栈中引用的对象(栈帧中的本地变量表)
  2. 方法区中的常量引用的对象
  3. 方法区中的类静态属性引用的对象
  4. 本地方法栈中 JNI(Native方法)的引用对象
  5. 活跃线程的引用对象

二、垃圾回收算法

1、标记 - 清除算法(Mark and Sweep)

  1. 标记: 从根集合进行扫描,对存活的对象进行标记
  2. **清除:**对堆内存从头到尾进行线性遍历,回收不可达对象内存
  3. 碎片化
    Java 垃圾回收机制(GC)

2、复制算法(Copying)

  1. 分为对象面和空闲面
  2. 对象在对象面上创建
  3. 存活的对象被从对象面复制到空闲面
  4. 将对象面所有对象内存清除
  • 特点
  1. 解决碎片化问题题
  2. 顺序分配内存,简单高效
  3. 适用于对象存活率低的场景
    Java 垃圾回收机制(GC)

3、标记 - 整理算法(Compacting)

  1. 标记:从根集合进行扫描,对存活的对象进行标记
  2. 清除:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收
  • 特点
  1. 避免内存的不连续行
  2. 不用设置两块内存互换
  3. 适用于存活率高的场景
    Java 垃圾回收机制(GC)

4、分代收集算法(Generational Collector)

  1. 垃圾回收算法的组合拳
  2. 按照对象生命周期的不同划分区域以采用不同的垃圾回收算法
  3. 目的:提高 JVM 的回收效率
  • JDK6、JDK7
    Java 垃圾回收机制(GC)
  • JDK8+
    Java 垃圾回收机制(GC)
  • GC的分类
  1. **Minor GC: ** Minor 区满时的垃圾回收操作。

    年轻代中的垃圾回收器,年轻代是几乎所有的 java 对象出生的地方,即 java 对象申请的内存及存放都是在该区域,通常大部分对象不会存活太长的时间,采用 复制算法 进行垃圾回收。

  2. Full GC: 对老年代的垃圾回收,也伴随这对新生代的垃圾回收。
  • 年轻代

    尽可能快速地收集掉那些生命周期短的对象。

    1. Eden区: (伊甸园区)新生成对象所在的位置,若空间不够,会放置在Survivo区等。
    2. 两个Survivor区: From区 和 To 区
      Java 垃圾回收机制(GC)

每次分配对象时,使用 Eden区 和 From区。当垃圾回收时,将 Eden区 和 From区中存活的对象,移动到 To区,然后清理掉 Eden区 和 From区。如果空间不够用将使用老年代中区域。

Java 垃圾回收机制(GC)
  • 对象如何晋升到老年代
  1. 经历一定 Minor(Eden区满时的操作) 次数(默认15次)依然存活的对象, (-XX:MaxTenuringThreshold 设置进入老年代的次数)
  2. Survivor区中存放不下的对象
  3. 新生成的大对象(-XX:+PretenuerSizeThreshold)
  • 常用的调优参数
  1. -XX:SurvivorRatio:Eden和Survivor的比值,默认8:1
  2. -XX:NewRatio:老年代和年轻代内存大小的比例
  3. -XX:MaxTenuringThreshold:对象从年轻代晋升到老生代经过GC次数的最大國值
  • 老年代

    存放生命周期较长的对象。采用标记-清理算法 或 标记-整理算法 进行垃圾回收。

    • Full GC 和 Major GC
    1. Major GC 是清理老年代。
    2. Full GC 是清理整个堆空间—包括年轻代和老年代。
    3. Full GC 比 Minor GC慢,但执行频率低。
    • 触发 Full GC 的条件
    1. 老年代空间不足
    2. 永久代空间不足 (JDK8以后不存在该情况)
    3. CMS GC时出现 promotion failed,concurrent mode failure
    4. Minor GC 晋升到老年代的平均大小大于老年代的剩余空间
    5. 调用System.gc(),请求 JVM 执行 垃圾回收,JVM 并不一定执行。
    6. 使用 RMI 来进行RPC 或 管理的JDK应用,每小时执行1次 Full GC

三、新生代垃圾收集器

1、Stop-the-World

  1. JVM 由于要执行GC 而停止了应用程序的执行,即执行 GC线程时,其他线程均处于等待状态。
  2. 任何一种GC算法中都会发生
  3. 多数 GC优化 通过减少 Stop-the-world发生的时间来提高程序性能

2、Safepoint

  1. 分析过程中对象引用关系不会发生变化的点(可进入执行 GC 的点)
  2. 产生 Safepoint的地方:方法调用;循环跳转;异常跳转等
  3. 安全点数量得适中

3、JVM模式

  1. **Server 模式:**启动慢,采用重量级虚拟机,对程序采用了更多的优化,运行稳定时速度会更快。
  2. Client 模式: 启动快,采用轻量级虚拟机,运行稳定时速度较 Server模式 慢。
    Java 垃圾回收机制(GC)

4、垃圾收集器之间的联系

Java 垃圾回收机制(GC)

5、年轻代常见的垃圾收集器

  • Serial 收集器(-XX:+UseSerialGC,复制算法)
  1. 单线程收集,进行垃圾收集时,必须暂停所有工作线程
  2. 简单高效,Client模式下默认的年轻代收集器
    Java 垃圾回收机制(GC)
  • ParNew 收集器(-XX:+UseParNewGC,复制算法)
  1. 多线程收集,其余的行为、特点和Serial收集器一样
  2. 单核执行效率不如Serial,在多核下执行才有优势,它默认开启的收集线程数与CPU的数量相同,在CPU非常多,可以使用 -XX:ParallelGCThreads参数来限制垃圾收集的线程数。
    Java 垃圾回收机制(GC)
  • Parallel Scavenge 收集器(-XX:+UseParallelGC,复制算法)
  1. 吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)
  2. 比起关注用户线程停顿时间,更关注系统的吞吐量,适合进行后台运算,不需要过多的交互运算
  3. 在多核下执行才有优势,Server模式下默认的年轻代收集器
  4. 可配合 -XX:+UseAdaptiveSizePolicy (自适应调节策略),将内存管理的调节任务交给虚拟机去完成。
    Java 垃圾回收机制(GC)

四、老年代常见的垃圾收集器

1、Serial Old 收集器(-XX:+UseSerialOldGC,标记-整理算法)

  1. 单线程收集,进行垃圾收集时,必须暂停所有工作线程
  2. 简单高效,Client模式下默认的老年代收集器
    Java 垃圾回收机制(GC)

2、Parallel Old 收集器(-XX:+UseParallelOldGC,标记-整理算法)

​ 多线程,吞吐量优先

Java 垃圾回收机制(GC)

3、CMS收集器(-XX:UseConcMarkSweepGC,标记-清除算法)

  1. 初始标记:stop-the-world
  2. 并发标记:并发追溯标记,程序不会停顿
  3. 并发预清理:查找执行 并发标记阶段从年轻代晋升到老年代的对象
  4. 重新标记:暂停虚拟机,扫描CMS堆中的剩余对象 stop-the-world
  5. 并发清理:清理垃圾对象,程序不会停顿
  6. 并发重置:重置CMS收集器的数据结构
    Java 垃圾回收机制(GC)
    该收集器 采用标记 - 清除算法,不会压缩存活的对象,会产生内存空间碎片化,如果要分配一个连续较大内存空间就只能触发一次GC。

4、G1收集器(-XX:+UseG1GC,复制+标记-整理算法)

  • Garbage First 收集器的特点
    1. 并行和并发
    2. 分代收集(独立管理整个堆,采用不同的算法对不同区中的垃圾进行收集)
    3. 空间整合
    4. 可预测的停顿
    5. 将整个Java堆内存划分成多个大小相等的Region
    6. 年轻代和老年代不再物理离
      Java 垃圾回收机制(GC)

5、JDK11新增的GC

​ Epsilon GC 和 ZGC

五、常见问题

1、Object 的 finalize()方法的作用 是否 与C++的析构函数作用相同

  1. 与C++的析构函数不同,析构函数调用时机是确定(离开其作用域),而它的是不确定的
  2. 当垃圾回收器寻找一个对象死亡时,至少要经过两次标记过程,如果对象在进行 可达性分析后,没有和 GC Root 相连接的引用链,就会被第一次标记,并且判断是否执行 finalize()方法,如果对象覆盖 finalize()方法且未被引用过,这个对象将放置于F-Queue队列中,并在稍后有 虚拟机自动建立的 低优先级的 finalize线程去执行触发 finalize方法。由于优先级低,不承诺其等待运行结束,即方法执行随时可能被终止。
  3. 方法执行随时可能会被终止
  4. 给予对象最后一次重生的机会
public class Finalization {
    public static Finalization finalization;
    @Override
    protected void finalize(){
        System.out.println("Finalized");
        finalization = this; // 使得垃圾回收不回收此对象(不建议)
    }

    public static void main(String[] args) {
        Finalization f = new Finalization();
        System.out.println("First print: " + f);
        f = null;
        System.gc();
        try {  // 休息一段时间,让垃圾回收线程执行完成上面的立即回收
            Thread.currentThread().sleep(1000);
        } catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("Second print: " + f);
        System.out.println(f.finalization);
    }
}
           
Java 垃圾回收机制(GC)

2、Java中的强引用,软引用,弱引用,虚引用有什么用

  • 强引用(Strong Reference)
  1. 最普遍的引用:Object obj = new Object()
  2. 抛出OutOfMemoryError终止程序也不会回收具有强引用的对象
  3. 通过将对象设置为null 来弱化引用,使其被回收
  • 软引用(Soft Reference)
  1. 对象处在有用但非必须的状态
  2. 只有当内存空间不足时,GC会回收该引用的对象的内存
  3. 可以用来实现高速缓存
String str = new String("abc");  // 强引用
SoftReference<String> softRef = new SoftReference<String>(str);  // 软引用
           
  • 弱引用(Weak Reference)
  1. 非必须的对象,比软引用更弱一些
  2. GC时会被回
  3. 被回收的概率也不大,因为GC线程优先级比较低
  4. 适用于引用偶尔被使用 且不影响垃圾收集的对象
String str = new String("abc");  // 强引用
WeakReference<String> weakRef = new WeakReference<String>(str);  // 弱引用
           
  • 虚引用(PhantomReference)
  1. 不会决定对象的生命周期
  2. 任何时候都可能被垃圾收集器回收
  3. 跟踪对象被垃圾收集器回收的活动,起哨兵作用
  4. 必须和引用队列 ReferenceQueue 联合使用
String str = new String("abc"); 
ReferenceQueue queue = new ReferenceQueue();  // 引用队列
PhantomReference ref = new PhantomReference(str, queue); // 虚引用
           
  • 强引用 > 软引用 > 弱引用 > 虚引用
    引用类型 被垃圾回收时间 用途 生存时间
    强引用 从来不会 对象的一般状态 JVM 停止运行时终止
    软引用 在内存不足时 对象缓存 内存不足时终止
    弱引用 在垃圾回收时 对象缓存 GC 运行后终止
    虚引用 Unknown 标记、哨兵 Unknown
  • 类层次结构
    Java 垃圾回收机制(GC)
  • 引用队列(ReferenceQueue)
  1. 无实际存储结构,存储逻辑依赖于内部节点之间的关系来表达
  2. 存储关联的且 被GC的软引用,弱引用以及虚引用
// 1
public class NormalObject {
    public String name;
    public NormalObject(String name){
        this.name = name;
    }

    @Override
    protected void finalize(){
        System.out.println("Finalizing obj " + name);
    }
}

// 2
public class NormalObjectWeakReference extends WeakReference<NormalObject> {
    public String name;

    public NormalObjectWeakReference(NormalObject normalObject, ReferenceQueue<NormalObject> rq) {
        super(normalObject, rq);
        this.name = normalObject.name;
    }
    @Override
    protected void finalize(){
        System.out.println("Finalizing NormalObjectWeakReference " + name);
    }
}

// 3 Test
public class ReferenceQueueTest {
    private static ReferenceQueue<NormalObject> rq = new ReferenceQueue<NormalObject>();

    private static void checkQueue(){
        Reference<NormalObject> ref = null;
        while ((ref = (Reference<NormalObject>)rq.poll()) != null){
            if (ref != null){
                System.out.println("In queue: " + ((NormalObjectWeakReference)(ref)).name);
                System.out.println("reference object:" + ref.get());
            }
        }
    }

    public static void main(String[] args) {
        ArrayList<WeakReference<NormalObject>> weakList = new ArrayList<WeakReference<NormalObject>>();
        for (int i =0; i < 3 ; i++){
            weakList.add(new NormalObjectWeakReference(new NormalObject("Weak " + i),rq));
            System.out.println("Created weak:" + weakList.get(i));
        }
        System.out.println("first time");
        checkQueue();
        System.gc();
        try {
            Thread.currentThread().sleep(1000);
        } catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("second time");
        checkQueue();
    }
}
           
Java 垃圾回收机制(GC)