天天看点

JVM专题(十)-垃圾回收(一)

文章目录

  • ​​1.如何判断对象可以回收​​
  • ​​1.1.引用计数法​​
  • ​​1.2.可达性分析算法​​
  • ​​2.五种引用​​
  • ​​6.代码示例​​
  • ​​6.1软引用​​
  • ​​6.2 弱引用​​

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

1.1.引用计数法

  • 当一个对象被其他变量引用,该对象计数加一,当某个变量不在引用该对象,其计数减一
  • 当一个对象引用没有被其他变量引用时,即计数变为0时,该对象就可以被回收
缺点:循环引用时,两个对象的计数都为1,导致两个对象都无法被释放
JVM专题(十)-垃圾回收(一)

1.2.可达性分析算法

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

    – 虚拟机栈(栈帧中的本地变量表)中引用的对象。

    – 方法区中类静态属性引用的对象

    – 方法区中常量引用的对象

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

    – 所有被同步锁(​​

    ​synchronized​

    ​关键字)持有的对象。

示例

/**
 * 演示GC Roots
 */
public class Demo2_2 {

    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...");
    }
}      

运行程序,分别生成垃圾回收前后的​

​dunp​

​文件

jps // 查看进程号
// format=b 生成文件格式为二进制;live主动触发垃圾回收,保留存活对象;file表示存放位置
jmap -dump:format=b,live,file=1.bin 进程号51125      

list1置空前,生成dunmp文件1.bin

JVM专题(十)-垃圾回收(一)
List<Object> list1 = new ArrayList<>();      

​list1​

​​是局部变量,存在于活动栈帧;​

​new ArrayList<>()​

​​产生的对象才是存在于堆中的对象。即此处​

​new ArrayList<>()​

​对应的那个对象才能作为根对象。

list1置空后,生成dunmp文件2.bin

JVM专题(十)-垃圾回收(一)

因为在执行

jmap -dump:format=b,live,file=2.bin 51125      

使用了live参数,主动调用了垃圾回收。由于​

​list1​

​​被置空,​

​list​

​对象无人引用,所以被垃圾回收了。所以在根对象中找不到了。

2.五种引用

JVM专题(十)-垃圾回收(一)

强引用

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

强引用对象回收

JVM专题(十)-垃圾回收(一)

软引用

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

软引用对象回收

JVM专题(十)-垃圾回收(一)
JVM专题(十)-垃圾回收(一)

此时A2对象可能被回收。

  • A2对象仅仅只被软引用对象引用
  • 在执行GC时,内存空间不足了,才会被垃圾回收
  • 回收后,软引用对象本身可以通过进入引用队列进行释放

弱引用

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

弱引用对象的回收

JVM专题(十)-垃圾回收(一)

此时A3对象可能会被回收

  • A3对象仅仅被弱引用对象引用
  • 当执行GC时,无论内存是否不足,都会被垃圾回收
  • 回收后,弱引用对象本身可以通过进入引用队列进行释放

虚引用

必须配合【引用队列】使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将【虚引用】入队, 由 Reference Handler 线程调用虚引用相关方法释放【直接内存】
如上图,B对象不再引用ByteBuffer对象,ByteBuffer就会被回收。但是直接内存中的内存还未被回收。这时需要将虚引用对象Cleaner放入引用队列中,然后调用它的clean方法来释放直接内存      

虚引用对象的回收

JVM专题(十)-垃圾回收(一)
JVM专题(十)-垃圾回收(一)

虚引用一般是对直接内存分配的应用。

  • 当声明​

    ​ByteBuffer​

    ​时,​

    ​ByteBuffer​

    ​会分配一块直接内存,并把直接内存的地址传递给虚引用对象Cleaner。
  • 当​

    ​ByteBuffer​

    ​不再被强引用时,被回收后,直接内存还没有被释放。这时会将虚引用放入虚引用的引用队列,由​

    ​Reference Handler​

    ​线程监控,发现虚引用对象,调用虚引用相关方法​

    ​Unsafe.freeMemory​

    ​释放直接内存。

终结器引用

无需手动编码,但其内部配合【引用队列】使用,在垃圾回收时,【终结器引用】入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过【终结器引用】找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象
如上图,B对象不再引用A4对象。这时终结器对象就会被放入引用队列中,引用队列会根据它,找到它所引用的对象。然后调用被引用对象的finalize方法。调用以后,该对象就可以被垃圾回收了      

终结器引用的回收

JVM专题(十)-垃圾回收(一)
  • 所有的类都继承自Object类,里面有一个终结方法finalize()方法,当对象重写了finalize()方法,且没有强引用引用它时,它就可以被当成垃圾进行垃圾回收。
  • 当对象没有被强引用时,会由jvm为该对象创建一个对应的终结器引用。当这个对象被垃圾回收时,会将终结器引用加入引用队列,但是对象不会被垃圾回收。
  • 再由一个优先级较低的Finalizer线程去监控引用队列是否有终结器引用,如果有,就通过终结器引用找到A4对象,调用其finalize()方法,等调用之后,等下一次垃圾回收时,就可以被垃圾回收了。
  • 工作效率低,第一次GC不会回收对象,先将终结器引用入队,等到第二次垃圾回收才有可能被回收。

6.代码示例

6.1软引用

# 虚拟机参数
-Xmx20m -XX:+PrintGCDetails -verbose:gc      
/**
 * 演示软引用
 * -Xmx20m -XX:+PrintGCDetails -verbose:gc
 */
public class Demo2_3 {

    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]);
        }

        System.in.read();*/
        soft();


    }

    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());
        }
    }
}      
JVM专题(十)-垃圾回收(一)
/**
 * 演示软引用, 配合引用队列
 */
public class Demo2_4 {
    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());
        }

    }
}      
JVM专题(十)-垃圾回收(一)

6.2 弱引用

-Xmx20m -XX:+PrintGCDetails -verbose:gc      
/**
 * 演示弱引用
 * -Xmx20m -XX:+PrintGCDetails -verbose:gc
 */
public class Demo2_5 {
    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());
    }
}      

继续阅读