12 垃圾回收

1. 什么是垃圾、为什么需要GC

  • 程序运行里,没有指针引用的数据
  • 内存不停消耗,尽早被消耗完

内存泄漏、内存溢出

  • 内存泄漏:内存中存在无法回收的垃圾占用
  • 内存溢出:分配内存时,内存不够,垃圾回收后仍无法满足

阶段

  • 标识阶段
    • 引用计数法
    • GC Roots 可达性算法
  • 回收阶段
    • 标记-清理
    • 标记-压缩
    • 复制

2 识别垃圾

  • 引用计数法
    • java未采用此算法
    • GC Roots 可达性算法/根搜索方法
  • GC roots包括元素
    • 虚拟机栈中引用的对象
    • 本地方法栈内JNI(通常说的本地方法)引用的对象
    • 方法区中类中静态属性引用的对象
    • 方法区中常量引用的对象
    • 所有被同步锁synchronized持有的对象
    • Java虚拟机内部的引用
      • 基本数据类型对应的class对象,一些常驻的异常对象(如:NullPointerException、OutOfMemeryError),系统类加载器
    • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
    • 分代收集、局部回收

3 对象的finalization机制

对象被终止(finalization)机制来允许开发人员提供对象被销毁前的自定义处理逻辑

  • finalize()可能导致对象复活
  • finalize()方法执行时间没有保障,完全由gc线程决定
  • 一个糟糕的finalize()会严重影响GC的性能

对象存在三中可能状态:可触及、可复活、不可触及

4 垃圾清理

Mark Sweep(标记清除)Mark Compact(标记压缩)Copying(拷贝)
速度中等最慢最快
空间开销少(存在碎片堆积)少(不堆积碎片)需要存活对象2位空间大小(不堆积碎片)
移动对象
  • 标记清除
    • 缺点:
      • 效率不高
      • 在进行GC时,STW,用户体验差
      • 清理出来的空间不连续,产生碎片,需要维护一个空闲列表
    • 清除只是把需要清除的对象地址保存在空间的地址列表里

5 垃圾回收并行并发

概念

  • 并发
    • 一段时间段内几个程序都处于启动运行到运行完毕之间。
  • 并行
    • 同一时间点cpu执行多项任务(一般一个cpu存在多核)

垃圾回收

  • 并行Parallel
  • Seial

DEMO

package nohi.jvm._12_GC;

import org.junit.Test;

/**
 * <h3>thinkinjava</h3>
 *
 * @author NOHI
 * @description <p></p>
 * @date 2022/11/26 16:04
 **/
public class LocalVarGC {

    public void localVarGc1() {
        byte[] bs = new byte[10 * 1024 * 1024];
        System.gc();
    }

    public void localVarGc2() {
        byte[] bs = new byte[10 * 1024 * 1024];
        bs = null;
        System.gc();
    }

    public void localVarGc3() {
        {
            byte[] bs = new byte[10 * 1024 * 1024];
        }
        System.gc();
    }

    public void localVarGc4() {
        {
            byte[] bs = new byte[10 * 1024 * 1024];
        }
        int i = 0;
        System.gc();
    }

    public void localVarGc5() {
        localVarGc1();
        System.gc();
    }

    /**
     * -XX:+PrintGCDetails
     * @param args
     */
    public static void main(String[] args) {
        LocalVarGC l = new LocalVarGC();
        // localVarGc1 不回收
        // [GC (System.gc()) [PSYoungGen: 14172K->10720K(76288K)] 14172K->10812K(251392K), 0.0065290 secs] [Times: user=0.07 sys=0.01, real=0.01 secs]
        // [Full GC (System.gc()) [PSYoungGen: 10720K->0K(76288K)] [ParOldGen: 92K->10656K(175104K)] 10812K->10656K(251392K), [Metaspace: 3111K->3111K(1056768K)], 0.0063655 secs] [Times: user=0.02 sys=0.02, real=0.00 secs]
        // [GC (System.gc()): young gc时未回收
        // Full GC: 到老年代
        // l.localVarGc1();

        // 回收
        // [GC (System.gc()) [PSYoungGen: 14172K->656K(76288K)] 14172K->664K(251392K), 0.0011722 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
        // [Full GC (System.gc()) [PSYoungGen: 656K->0K(76288K)] [ParOldGen: 8K->416K(175104K)] 664K->416K(251392K), [Metaspace: 3111K->3111K(1056768K)], 0.0040826 secs] [Times: user=0.03 sys=0.00, real=0.00 secs]
        // l.localVarGc2();

        // 不回收
        // [GC (System.gc()) [PSYoungGen: 14172K->10720K(76288K)] 14172K->10828K(251392K), 0.0060878 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
        // [Full GC (System.gc()) [PSYoungGen: 10720K->0K(76288K)] [ParOldGen: 108K->10655K(175104K)] 10828K->10655K(251392K), [Metaspace: 3105K->3105K(1056768K)], 0.0065082 secs] [Times: user=0.02 sys=0.03, real=0.01 secs]
        // l.localVarGc3();

        // 回收
        // [GC (System.gc()) [PSYoungGen: 14172K->704K(76288K)] 14172K->712K(251392K), 0.0012136 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
        // [Full GC (System.gc()) [PSYoungGen: 704K->0K(76288K)] [ParOldGen: 8K->416K(175104K)] 712K->416K(251392K), [Metaspace: 3111K->3111K(1056768K)], 0.0041112 secs] [Times: user=0.02 sys=0.01, real=0.01 secs]
        // l.localVarGc4();

        // 回收
        // [GC (System.gc()) [PSYoungGen: 14172K->10720K(76288K)] 14172K->10808K(251392K), 0.0058146 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
        // [Full GC (System.gc()) [PSYoungGen: 10720K->0K(76288K)] [ParOldGen: 88K->10656K(175104K)] 10808K->10656K(251392K), [Metaspace: 3112K->3112K(1056768K)], 0.0057435 secs] [Times: user=0.02 sys=0.03, real=0.00 secs]
        // [GC (System.gc()) [PSYoungGen: 0K->0K(76288K)] 10656K->10656K(251392K), 0.0004863 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
        // [Full GC (System.gc()) [PSYoungGen: 0K->0K(76288K)] [ParOldGen: 10656K->416K(175104K)] 10656K->416K(251392K), [Metaspace: 3112K->3112K(1056768K)], 0.0038133 secs] [Times: user=0.03 sys=0.00, real=0.00 secs]
        // 前两个:GC、Full GC为方法一中
        // 后两个:GC、Full Gc 为localVarGc5方法中,由于方法一已经执行完,可以被回收
        l.localVarGc5();
    }
}

6 安全点(Safe Point)、安全区域(Safe Region)

  • 安全点:程序执行时并非在所有地方都能停顿下开始GC,只有在特定位置才能停顿去执行GC,这些位置称为安全点(Safepoint)
    • 方法调用、循环跳转、异常跳转
    • 抢先式中断
    • 主动式中断
  • 安全区域:一段代码版本中,对象的引用关系不会发生变化,在这个区域中的任何位置开始GC都是安全的

7 引用

  • 强引用、软引用、弱引用、虚引用有什么区别、具体使用场景是什么?
    • 强引用:代码普遍存在,类似Object obj = new Ojbect()这种引用关系,无论任何情况下,些引用关系存在,则不会回收
    • 软件引用:内存溢出之前,将会把这些对象列入回收,范围之中进行第二次回收,如果回收后没有足够内存,抛溢出异常。
    • 弱引用:被弱引用关系的对象只能生存到下次垃圾回收之前,即垃圾回收时,无论空间是否足够,都会回收弱引用关系对象。
    • 虚引用:一个对象是否有虚引用存在,完全不会对期生存时间构成影响,也无法通过虚引用来获得一个对象的实例,为一个对象设置虚引用的唯一目的就是在这个对象被收集器回收时收到一个系统通知。

强引用-不回收

软引用-内在不足即回收

当内存足够时,不会回收软件引用可达对象

当内存不够时,会回收软件引用可达对象

Object obj = new Object();// 声明强引用
SoftReference<Object> sf = new SoftReference(obj);
obj = null;// 销毁引用 

虚引用-对象回收跟踪

唯一目的在于跟踪垃圾回收过程。比如:能在这个对象被收集器回收时收到一个系统通知

Object obj = new Object()
PhantomQueue phantomQueue = new PhantomQueue();
PhantomReference<Object> pr = new  PhantomReference<Object>(obj, phantomQueue);
上次更新:
贡献者: NOHI