天天看点

JVM 垃圾回收(一)如何判断对象可以回收

垃圾回收的方式

1. 引用计数法

表示在底层实现的时候使用一个用于计数的变量进行计数,在每一次对一个变量或者实例进行引用之后,计数加一,当引用不使用之后,计数减一,直到计数为零的时候,这就代表着当前的这个变量或者实例没有被引用,也就是说可以被当成垃圾进行回收。

在这里,也会存在一个弊端,就是循环引用,在A和B对象之间一直互相引用,计数一直增加,就无法确定在什么时候才能进行回收。

JVM 垃圾回收(一)如何判断对象可以回收

2. 可达性分析算法

Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象

扫描堆中的对象,看是否能够沿着 GC Root 对象为起点的引用链找到该对象,找不到,表示可以回收

那怎么查看这个GC root 有哪些呢,我们使用一段简单的代码进行测试,在这里检测两个状态,运行代码。

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class GC_root {
    public static void main(String[] args) throws InterruptedException, IOException {
        List<Object> list1 = new ArrayList<>();
        list1.add("a");
        list1.add("b");
        System.out.println(1);
        System.in.read();

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

使用jmap工具抓取进程快照,使用命令 jmap -dump:format=b,live,file=1.bin 8208

  • b 表示二进制文件
  • live 当前还存活的对象,也就是会触发一次垃圾回收
  • file 表示将快照抓取出来,另存为一个什么文件
  • 最后面表示当前进程id

使用命令进行检测

JVM 垃圾回收(一)如何判断对象可以回收

得到了两个文件,但是怎么进行分析呢,在这里我们使用eclipes提供的MAT工具

MAT内存分析工具

MAT是Memory Analyzer的简称,它是一款功能强大的Java堆内存分析器。可以用于查找内存泄露以及查看内存消耗情况。MAT是基于Eclipse开发的,是一款免费的性能分析工具。可以在http://www.eclipse.org/mat/下载并使用MAT。

下载完成之后进行解压缩,双击exe文件打开。之后打开这两个bin文件,如下图所示就可以查看gc root

JVM 垃圾回收(一)如何判断对象可以回收

打开GC root 查看,分了四个大类:如下图所示:

JVM 垃圾回收(一)如何判断对象可以回收

3. 四种引用

  1. 强引用

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

  2. 软引用(SoftReference)

    仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象

    可以配合引用队列来释放软引用自身

  3. 弱引用(WeakReference)

    仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象

    可以配合引用队列来释放弱引用自身

  4. 虚引用(PhantomReference)

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

  5. 终结器引用(FinalReference)

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

4. 软引用案例

在这里,我们获取数据,但是我们的内存有限,而这里的资源又不是那么重要,(添加一个参数 -Xmx20m 进行模拟)我们使用强引用的话,会出现堆内存溢出的报错。使用以下代码测试

private static final int _4MB = 4 * 1024 * 1024;
    public static void main(String[] args) throws IOException {
        List<byte[]> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            list.add(new byte[_4MB]);
        }
           
JVM 垃圾回收(一)如何判断对象可以回收

使用软引用进行实现:添加一个软引用,在这里的连接引用也是由一个强引用之间加上了一个软引用。

public static void soft() {
        // list --> SoftReference --> byte[]

        List<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());
        }
    }
           

运行代码,添加了软引用之后,内存不足的时候会触发垃圾回收机制,所以在这里代码是不会导致堆内存溢出的。垃圾回收之后,list当中的值会被清除为null。

JVM 垃圾回收(一)如何判断对象可以回收
软引用与引用队列

在这里软引用虽然为null,但是也会占据内存空间,在这里我们使用一个引用队列对这个软引用进行清除。代码实现如下:

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;

public class queue {
    private static final int _4MB = 4 * 1024 * 1024;
    public static void main(String[] args) {
        List<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[]> reference : list) {
            System.out.println(reference.get());
        }
    }
}
           

在最后获取这个list就只会存在一个数值,其余的null值均被清空。

JVM 垃圾回收(一)如何判断对象可以回收
弱引用

弱引用对象我们和前面的测试一致,一直添加,并且我们给虚拟机加上参数 -XX:+PrintGCDetails -verbose:gc 表示打印垃圾回收的相关信息。

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
public class WeakReference_test {
    private static final int _4MB = 4 * 1024 * 1024;
    public static void main(String[] args) {
        //  list --> WeakReference --> byte[]
        List<WeakReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            WeakReference<byte[]> ref = new WeakReference<>(new byte[_4MB]);
            list.add(ref);
            for (WeakReference<byte[]> w : list) {
                System.out.print(w.get()+" ");
            }
            System.out.println();
        }
        System.out.println("循环结束:" + list.size());
    }
}
           

运行代码,查看结果(在这里,我们也可以将-Xmx设置的小一点,这样的话垃圾回收机制就会被触发多次)在这里没有设置,在第七次执行后,第八次执行时,内存不够就会调用垃圾回收机制,将前面没有用的变量所占的内存进行回收,

JVM 垃圾回收(一)如何判断对象可以回收