JVM学习笔记2——垃圾回收GC

发布时间 2023-08-08 18:32:20作者: Avava_Ava

三、垃圾回收

 

1.如何判断对象是否可以回收

 

①引用计数法——早期python中使用

当一个对象被引用时,就当引用对象的值加一,当值为 0 时,就表示该对象不被引用,可以被垃圾收集器回收。
这个引用计数法听起来不错,但是有一个弊端,如下图所示,循环引用时,两个对象的计数都为1,导致两个对象都无法被释放。

 

 

 

②可达性分析算法——JVM使用

VM 中的垃圾回收器通过可达性分析来探索所有存活的对象
扫描堆中的对象,看能否沿着 GC Root 对象为起点的引用链找到该对象,如果找不到,则表示可以回收


可以作为 GC Root 的对象有:

Ⅰ. 虚拟机栈(栈帧中的本地变量表)中引用的对象;

Ⅱ. 方法区中类静态属性引用的对象;

Ⅲ. 方法区中常量引用的对象;

Ⅳ. 本地方法栈中 JNI(即一般说的Native方法)引用的对象;

 

 

案例演示:

public static void main(String[] args) throws IOException {

        ArrayList<Object> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add(1);
        System.out.println(1);
        System.in.read();

        list = null;
        System.out.println(2);
        System.in.read();
        System.out.println("end");
    }

对于以上代码,可以使用如下命令将堆内存信息转储成一个文件,然后使用
Eclipse Memory Analyzer 工具进行分析。


第一步:
使用 jps 命令,查看程序的进程

 

第二步:

使用 jmap -dump:format=b,live,file=1.bin 16104 命令转储文件
dump:转储文件
format=b:二进制文件
file:文件名
16104:进程的id

 

第三步:打开 Eclipse Memory Analyzer 对 1.bin 文件进行分析。

分析的 gc root,找到了 ArrayList 对象,然后将 list 置为null,再次转储,那么 list 对象就会被回收

 

 

 

 

③四种引用

强引用:

只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收

 

软引用(SoftReference):

仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象,可以配合引用队列来释放软引用自身

 

弱引用(WeakReference):

仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象,可以配合引用队列来释放弱引用自身

 

虚引用(PhantomReference):

必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存

 

终结器引用(FinalReference):

无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象。

 

 

Ⅰ.软引用&引用队列案例演示——SoftReference

/**
 * 演示 软引用
 * -Xmx20m -XX:+PrintGCDetails -verbose:gc
 */
public class Code_08_SoftReferenceTest {

    public static int _4MB = 4 * 1024 * 1024;

    public static void main(String[] args) throws IOException {
        method2();
    }

    // 强引用,设置 -Xmx20m , 演示堆内存不足,
    public static void method1() throws IOException {
        ArrayList<byte[]> list = new ArrayList<>();

        for(int i = 0; i < 5; i++) {
            list.add(new byte[_4MB]);
        }
        System.in.read();
    }

    // 演示 软引用
    public static void method2() throws IOException {
        ArrayList<SoftReference<byte[]>> list = new ArrayList<>();
        for(int i = 0; i < 5; i++) {
            SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);
            System.out.println(ref.get());
            list.add(ref);
            System.out.println(list.size());
        }
        System.out.println("循环结束:" + list.size());
        for(SoftReference<byte[]> ref : list) {
            System.out.println(ref.get());
        }
    }
}

 

 method1 方法解析:

首先会设置一个堆内存的大小为 20m,然后运行 mehtod1 方法,会抛异常,堆内存不足,因为 mehtod1 中的 list 都是强引用

 

 method2 方法解析:

在 list 集合中存放的是软引用对象,当内存不足时,会触发 full gc,将软引用的对象回收。细节如图:

可以看到第五次循环时发现内存不足,触发了垃圾回收,将前4个软引用对象全部回收;

 

 

 

 上面的代码中,当软引用引用的对象被回收了,但是软引用还存在,所以,一般软引用需要搭配一个引用队列一起使用。

修改 method2 如下:

// 演示 软引用 搭配引用队列
    public static void method3() throws IOException {
        ArrayList<SoftReference<byte[]>> list = new ArrayList<>();
        // 引用队列
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();

        for(int i = 0; i < 5; i++) {
            // 关联了引用队列,当软引用所关联的 byte[] 被回收时,软引用自己会加入到 queue 中去
            SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
            System.out.println(ref.get());
            list.add(ref);
            System.out.println(list.size());
        }

        // 从队列中获取无用的 软引用对象,并移除
        Reference<? extends byte[]> poll = queue.poll();
        while(poll != null) {
            list.remove(poll);
            poll = queue.poll();
        }

        System.out.println("=====================");
        for(SoftReference<byte[]> ref : list) {
            System.out.println(ref.get());
        }
    }

 此时已经为空的软引用本身也被移除了,输出结果如下:

 

 

 Ⅱ.弱引用案例演示——WeakReference

弱引用
 public class Code_09_WeakReferenceTest {

    public static void main(String[] args) {
//        method1();
        method2();
    }

    public static int _4MB = 4 * 1024 *1024;

    // 演示 弱引用
    public static void method1() {
        List<WeakReference<byte[]>> list = new ArrayList<>();
        for(int i = 0; i < 10; i++) {
            WeakReference<byte[]> weakReference = new WeakReference<>(new byte[_4MB]);
            list.add(weakReference);

            for(WeakReference<byte[]> wake : list) {
                System.out.print(wake.get() + ",");
            }
            System.out.println();
        }
    }

    // 演示 弱引用搭配 引用队列
    public static void method2() {
        List<WeakReference<byte[]>> list = new ArrayList<>();
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();

        for(int i = 0; i < 9; i++) {
            WeakReference<byte[]> weakReference = new WeakReference<>(new byte[_4MB], queue);
            list.add(weakReference);
            for(WeakReference<byte[]> wake : list) {
                System.out.print(wake.get() + ",");
            }
            System.out.println();
        }
        System.out.println("===========================================");
        Reference<? extends byte[]> poll = queue.poll();
        while (poll != null) {
            list.remove(poll);
            poll = queue.poll();
        }
        for(WeakReference<byte[]> wake : list) {
            System.out.print(wake.get() + ",");
        }
    }

}

 

 

 

 

 

 2.垃圾回收算法

 

 ①标记清除Mark Sweep

对于没有被GC Root引用的对象做标记,然后直接清除

优点:速度较快

缺点:会产生内存碎片

 

 

 

 ②标记整理Mark Compact

对于有被GC Root引用的对象做标记并将其移动到连续内存空间中,其它全部清除

 

优点:没有内存碎片

缺点:速度慢

 

 

 ③复制Copy

将内存空间分为两部分FROM和TO,只在FROM区域存放对象,垃圾回收时先将有被GC Root引用的对象做标记并复制到TO区域中,

然后清除FROM区域的全部数据,最后让FROM和TO区域互换身份

 

优点:没有内存碎片

缺点:需要占用两倍内存空间

 

 

 

 

 

3.分代垃圾回收