垃圾回收之如何判断对象可以回收、四种引用以及实际案例操作

垃圾回收

JVM内存结构中的堆存在垃圾回收机制,我们接下来就来详细地学习一下垃圾回收的相关知识。

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

1.1 引用计数法

只要一个对象被其他变量所引用,那就让这个对象的计数+1,如果它被引用了两次,那就让它的计数变成2,如果某一个变量不再
引用它了,那让它的计数-1,当这个对象它的引用计数变为0的时候,意味着没有人再引用它了,那么它就可以作为一个垃圾进行
一个回收。但是引用计数法听起来很好,但是它存在一个重要的弊端,一种叫循环引用的问题,如下图

在这里插入图片描述

A对象引用了B对象,B对象的引用计数是1,反过来,B对象同时也引用了A对象,那A对象的引用计数也是1,它们各自的引用计数
都是1,由于它们的引用计数不能归零,导致这两个对象不能作为垃圾进行回收,这样就造成了内存上的泄露,据说早期的python
虚拟机它在进行垃圾回收的控制时,就是采用了引用计数的算法,我们的java虚拟机没有采用这种引用计数法,而是会采用我们
下节介绍的可达性分析算法。

1.2 可达性分析算法

  • Java虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
  • 扫描堆中的对象,看是否能够沿着 GC Root 对象为起点的引用链找到该对象,找不到,表示可以回收
  • 哪些对象可以作为 GC Root?
java虚拟机采用的判断对象是否是垃圾的算法是可达性分析算法。下面我们来学习一下可达性分析算法的原理
可达性分析算法首先要确定一系列根对象,那什么是根对象呢,所谓的根对象就是那些肯定不能当成垃圾被回收的对象,
我们就称之为根对象。在垃圾回收之前,我们首先会对堆内存中的所有对象进行一遍扫描,看看每一个对象是不是被
刚才提到的根对象所直接或间接地引用,如果是,那么这个对象就不能被回收,反之如果一个对象没有被根对象直接或
间接的引用,那么这个对象就可以作为垃圾将来可以被回收。比如:夏天大家都会吃葡萄这种水果,葡萄洗了以后,
可以提着葡萄的根把它向上一提,这样,连在根上的这些葡萄果它就是不能被回收的对象,因为有根来引用它们,而
与根断开落在盘子中的那些葡萄就是可以作为垃圾被回收的对象。
package Memory.GC;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

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

    public static void main(String[] args) throws 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...");
    }
}

在这里插入图片描述

程序执行到1,抓取的是list引用被置为空之前的状态:

在这里插入图片描述

程序执行到2,抓取当前状态:

在这里插入图片描述

MAT(Memory Analyzer Tool)下载和安装 - 周文豪 - 博客园 (cnblog

使用Memory Analyzer分析1.bin和2.bin两次状态的文件:

在这里插入图片描述

在这里插入图片描述

1时刻内存快照:

在这里插入图片描述

在这里插入图片描述

列出了当前对内存占用最多的那些对象都有哪些。
点击列出GC Roots的功能:
可以看到它把根对象分化成了好几类:
第一类叫System Class,它就是一些系统类,这些系统类都是由启动类加载器加载的类,也就是那些核心的类,
在运行期间肯定会用到的类,它们能够作为GC Root对象,它们肯定不会被垃圾回收,如果把核心的类回收掉了,
那其他的代码没办法运行了。
第二类是Native Stack,java虚拟机在执行一些方法调用时它必须调用操作系统的方法,操作系统方法在执行时
它所引用的一些java对象也是可以作为根对象,也是不能被垃圾回收掉的。
第四类叫做Busy Monitor,java对象中有一个同步锁机制,就是那个Synchronized关键字,Synchronized关键字
如果对一个对象加了锁,被加锁的对象是不能当成垃圾的,如果它能被当成垃圾,将来被回收掉了,将来谁来解锁呀,
解锁那个对象上的锁啊,都被回收掉了,所以正在加锁的对象它们也是可以作为根对象,它们所引用的其他对象,也是
需要被保留的。
第三类是一些活动线程,活动线程中所使用的一些对象,是不能被当成垃圾的,线程运行时都由一次次的方法调用组成,
每次方法调用都会产生一个栈帧,栈帧内所使用的一些东西它们可以作为根对象。比如主线程用到的一些变量,这些局部变量
所引用的对象都可以作为根对象,

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

引用变量和对象辨析:

在这里插入图片描述

lsit1只是一个引用,它是存在在活动栈帧里的,它是一个局部变量,那么它后面所引用的对象是存储在堆里的,
根对象也是指堆中的那些对象,而不是前面的局部变量引用。

2.bin文件中的主线程中的根对象:

在这里插入图片描述

2时刻快照文件2.bin中的主线程中根对象中没有ArrayList了,因为局部变量已经置为null了,也就是不再引用代码中的ArrayList
对象,接下来因为我们执行了jmap -dump:format=b,live,file=2.bin 144912,live参数会主动执行一次垃圾回收,
垃圾回收会把不再有人引用的ArrayList对象给回收掉,回收掉以后在根对象列表中就找不到它了。
大家以后查看根对象就可以通过MAT工具的可视化看哪些对象是我们的根对象。

1.3 四种引用

  1. 强引用
    • 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收。
  2. 软引用(Soft Reference)
    • 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用对象
    • 可以配合引用队列来释放软引用自身
  3. 弱引用(Weak Reference)
    • 仅有弱引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
    • 可以配合引用队列来释放弱引用自身
  4. 虚引用(Phantom Reference)
    • 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存
  5. 终结器引用(Final Reference)
    • 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize() 方法,第二次 GC 时才能回收被引用对象。

注意:引用和引用所引用的对象是不一样的,例如虚引用和虚引用所引用的对象是不一样的,虚引用也消耗一定的内存。

实线箭头表示强引用,虚线分别代表软、弱、虚、终结器引用,

在这里插入图片描述

软、弱引用:

在这里插入图片描述

在这里插入图片描述

# 软引用和弱引用的区别
当没有强引用引用A2、A3的时候,这时候分别只有软引用、弱引用来引用它俩,这时候发生了垃圾回收,垃圾回收之后如果内存不够,
会回收掉软引用的对象,不管内存够不够,都会回收掉弱引用引用的对象,这是软、弱这两种引用回收的时机。
我们先来看强引用,其实我们平时在用的所有引用都属于强引用,比如说我们new了一个对象,把这个对象通过等号这个赋值运算符,
赋值给了一个变量,那么这个变量就强引用了刚刚的对象,强引用的特点就是沿着GC Root的引用链能够找到它,那么它就不会被
垃圾回收,比如上图的C对象是一个GC Root,它是一个根对象,沿着根对象能找到A1这个对象,那么A1对象是不能被回收的,
当然GC Root B对象也强引用了A1对象,所以有两个GC Root对象都引用了A1对象,A1肯定是不能被垃圾回收的,当垃圾回收时就会
认为它应当被保留下来,那什么时候A1对象能够被垃圾回收呢,只有从GC Root对它的引用都断开时,这个强引用都断开时,它才能被
垃圾回收,当没有GC Root直接或间接的引用A1对象了,那么当垃圾回收发生时它就可以被回收掉了,这个我们称之为强引用。

那下面我们再来看软、弱这两种引用,软、弱引用跟强引用的一个区别就是只要A2、A3这两个对象没有被直接的强引用所引用,
那么当垃圾回收发生时,都有可能被回收掉。举个例子,现在A2对象被C对象间接地引用到了,它虽然是强引用到了一个软引用对象,
然后通过软引用对象引用到了A2对象,这算是一种间接地途径,中间是个软引用,当然A2对象同时被B对象的强引用直接引用了,
当然,这种情况下,垃圾回收时A2对象是不会被回收的,但是有一种情况,B GC Root对象不再强引用A2对象了,这个时候只有一个
软引用引用了A2对象,那么它只要满足了下面这个条件是可以被回收掉的,什么条件呢,就是当垃圾回收时并且内存不够时,就是
发生了一次垃圾回收,并且垃圾回收回收完了还发现内存不够,那它就会把软引用所引用的这个对象释放掉,它认为软引用引用的对象
不够重要,当内存不足时,需要把它占用的空间腾出来给其他更有用的对象来使用,所以它是当内存不足时会被回收掉,当然还要满足
没有其他强引用引用它。

那弱引用呢跟软引用非常的像,也是当没有强引用引用它的时候,并且它的回收条件更宽松一些,只要发生了垃圾回收,不管是不是
内存充足,都会把弱引用的这个对象回收掉。

再说一遍软引用和弱引用的区别:
当没有强引用引用A2、A3的时候,这时候分别只有软引用、弱引用来引用它俩,这时候发生了垃圾回收,垃圾回收之后如果内存不够,
会回收掉软引用的对象,不管内存够不够,都会回收掉弱引用引用的对象,这是软、弱这两种引用回收的时机。

软、弱引用还可以配合引用队列来一起工作,什么意思呢,就是当软引用的对象被回收掉了以后,那么软引用自身它也是一个
对象,那它如果在创建时给它分配了一个引用队列,那它在它所引用的对象被回收时,那么软引用就会进入这个队列,弱引用
也是类似,当弱引用引用的对象被垃圾回收掉了以后,那弱引用如果配合了引用队列的话,它也会进入弱引用的引用队列,
为什么要做这么一个处理呢,就是因为软引用也好,弱引用也好,它们自身也要占用一定的内存,如果想要对它俩占用
的内存做进一步的释放,那么需要使用引用队列,来找到它俩,可以对它做进一步地处理,因为它俩还可能被强引用所引用着,
所以把它俩在引用队列里依次遍历,把它俩占用的内存给它释放掉,这就是软、弱这两种引用的用法。

虚引用、终结器引用:

在这里插入图片描述

在这里插入图片描述

虚引用和终结器引用特点和前面的软、弱两种引用不同,软、弱既可以配合引用队列使用,也可以不配合引用队列使用,

而虚引用和终结器引用必须配合引用队列来使用,也就是当虚引用对象创建的时候,它就会关联一个引用队列,当
终结器引用对象被创建的时候,它也会关联一个引用队列,那么它们是怎么工作的呢,我们来举一个例子:
其实对于虚引用对象来讲,我们之前讲直接内存时其实接触过,讲直接内存时创建ByteBuffer的实现类对象时,
它就会创建一个叫做名为Cleaner的虚引用对象,ByteBuffer会分配一块直接内存,并且会把直接内存地址
传递给虚引用对象,做这样的一个操作是为了干嘛呢,将来ByteBuffer一旦没有强引用所引用它,ByteBuffer自己
可以被垃圾回收掉,但是它被垃圾回收了不够,因为它分配的直接内存并不能被java垃圾回收所管理,所以需要在
ByteBuffer被回收的时候,让虚引用对象进入引用队列,而虚引用所在的引用队列会有一个叫做Reference Handler
的线程定时的到引用队列里找,看有没有一个新入队的Cleaner,如果有,它就会调用Cleaner中的clean()方法,
clean()方法中就会根据前面记录的直接内存的地址然后调用Unsafe对象的freeMemory()方法,这个方法会把
直接内存释放掉,就不会造成直接内存导致的内存泄漏。

虚引用:
在虚引用引用的对象被垃圾回收时,虚引用对象自己就会放入引用队列,从而间接地由一个线程来调用虚引用对象的方法,
然后调用Unsafe对象的freeMemory()去释放那块直接内存。这就是虚引用它的一个典型用法。


终结器引用:
所有的java对象都会继承自Object父类,而Object中有一个finalize()方法,就是终结方法,当对象重写了终结方法
并且没有强引用引用它时,它就可以被当成垃圾进行回收,那么这个终结方法什么时候被调用呢,你重写了这个终结方法
你其实就希望这个终结方法将来在对象垃圾回收时被调用,那它就是靠终结器引用,来达到这个目的,当没有强引用引用
对象时,这个对象会由虚拟机创建终结器引用,当A4对象被垃圾回收时,它就会把终结器引用也加入引用队列,但是注意,
这个时候A4对象还没有被垃圾回收,它不是立刻垃圾回收,它是先把它对应的终结器引用放入引用队列,再由一个优先级很低
的线程,叫做Finaize Handler线程,它也会在某些时机去查看这个引用队列中是不是有终结器引用,结果发现有一个新加入的
终结器引用,那么它就会根据这个终结器引用找到要作为垃圾回收的A4对象并且调用A4对象的finalize()方法,等调用完了,
下一次垃圾回收时就可以把A4对象占用的内存真正回收掉,这就是终结器引用它的一个作用。不过其实finalize()方法其实
工作效率很低,它是第一次回收时还不能把A4对象真正回收掉,而是先把终结器引用入队,而且处理这个引用队列的线程
优先级很低,被执行的机会很少,所以就可能会造成A4对象的finalize()方法迟迟不被调用,这个对象占用的内存
也迟迟得不到释放,所以这也是为什么我们不推荐大家用finalize()去释放资源的一个理由,不推荐使用finalize()方法,
因为它实在是考虑的太复杂了。

软引用的应用场景:

package Memory.GC;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 演示软引用
 * 设置堆内存大小为20m
 * -Xmx20m -XX:+PrintGCDetails -verbose:gc
 * -Xmx20m是设置堆内存为20m,后面的-XX:+PrintGCDetails -verbose:gc是打印垃圾回收的详细信息
 */
public class Demo02 {
    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++){
            // 堆内存为20m,每次往list集合里加4MB的内存,循环了5次以后,肯定超过了堆内存大小了
            // 肯定会爆一个java.lang.OutOfMemoryError: Java heap space
            // 堆内存空间不足导致的内存溢出
            list.add(new Byte[_4MB]);
        }

        System.in.read();
    }
   
}

在这里插入图片描述

上面的代码会爆一个堆内存溢出问题,现实业务开发中这样的场景还是比较常见的,比如说上面的Byte数组可能是
读取自网络上的一些图片,这些图片显示的时候需要把它放在list集合里去进行一个显示,但是这些图片资源并不
属于核心业务资源,由于图片的数量过多,用强引用来引用这些图片资源就会导致刚才看到的内存溢出的错误,那
这些不太重要的资源能不能在内存紧张时把它占用的内存释放掉,以后如果再用到这张图片再读取一遍图片资源
不就行了吗,强引用就不能使用这个场景了,而是采用软引用,

软引用

package Memory.GC;

import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;

/**
 * 演示软引用
 * 设置堆内存大小为20m
 * -Xmx20m -XX:+PrintGCDetails -verbose:gc
 * -Xmx20m是设置堆内存为20m,后面的-XX:+PrintGCDetails -verbose:gc是打印垃圾回收的详细信息
 */
public class Demo02 {
    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++){
//            // 堆内存为20m,每次往list集合里加4MB的内存,循环了5次以后,肯定超过了堆内存大小了
//            // 肯定会爆一个java.lang.OutOfMemoryError: Java heap space
//            // 堆内存空间不足导致的内存溢出
//            list.add(new byte[_4MB]);
//        }
//
//        System.in.read();
    }

    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直接引用byte[]数组了,而是在它们之间加了一个SoftReference,
就是list先引用了软引用对象,软引用对象再间接地引用byte[]数组,list里面是软引用,软引用里面才是byte[]
数组,那么怎么构造这个软引用对象呢,很简单,就是跟我们普通对象一样,创建一个 new SoftReference<>(),
对象,它的内部再包装软引用所真正引用的byte数组对象,现在list和SoftReference之间是强引用,但SoftReference
对对byte[]数组就是一个软引用了。
运行以下上面的代码,看会不会造成内存的溢出
可以看到代码并没有引起内存的溢出,在循环当中我们发现调用软引用的get()方法也就是内部的byte[]数组
都是有的,但是等循环结束了我们再去循环一遍list,调用软引用的get去取的时候,发现集合的前四个元素
都变成了null,只有最后一个被保留下来了,

在这里插入图片描述

接下来我们加上-XX:+PrintGCDetails -verbose:gc参数来打印垃圾回收的详细信息

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

前四次循环把内存几乎占得满了,在放第5个byte[]数组时放不下了,发现一次完全的垃圾回收后内存空间
仍然不够,这就触发了软引用的内存回收,软引用的特性就是在一次垃圾回收后内存空间仍然不足时,它就会
把软引用所引用的对象给它释放掉,所以又触发了一次新的垃圾回收,这次垃圾回收以后我们看到新生代的
内存占用已经变成了0,老年代的内存占用也从12M变成了600多K,相当于把新生代、老年代都腾出来了,
其实就是把前四个byte[]数组对象占用的内存都给释放掉了,只有最后一个byte[]数组被放了进去,
程序运行结束时我们可以看到老年代只占了%7的内存,新生代占了78%,它的总大小是5M多,一个byte[]数组
是4M多,所以占用了%78左右的内存,也就是最后只剩了一个byte[]数组,其他软引用引用的byte[]数组对象
都被回收掉了,这就是软引用在内存敏感(内存空间有限)的应用程序下的好处,不重要的对象用软引用关联它,
这样的话空间紧张时就可以把它回收掉了。

演示软引用,配合引用队列

package Memory.GC;

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

/**
 * 演示软引用,配合引用队列
 * 设置堆内存大小为20m
 * -Xmx20m -XX:+PrintGCDetails -verbose:gc
 */
public class Demo03 {
    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){
            // 如果poll != null,说明队列里还有内容,有内容的话就从list中remove()掉这个软引用对象
            // 每循环一次就把它remove()掉,直到队列为空说明队列中的元素已经被取完了,这样的话就把
            // 那四个不需要的软引用对象从list集合中删除掉了,
            list.remove(poll);
            poll = queue.poll();
        }

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

在这里插入图片描述

在内存不足时会把软引用对应的byte[]数组对象的内存进行了一个释放,但是我们发现最后遍历list集合的时候其中一些
软引用的对象已经是null了,对这些软引用对象其实没必要再把它保留在list集合当中了,因为有效的只有最后一个,其他
为null的再循环也没有什么意义,所以遇到这种情况呢,我们希望把软引用本身也做一个清理,从list集合清除掉,软引用
对象本身它也是要占用内存的,虽然它占用的内存比较少,但是像取值已经为null的没必要再保留了,那么怎么实现清理无用
的软引用呢,我们就需要配合引用队列这么一个对象来完成软引用的清理。
引用队列对象通过ReferenceQueue<byte[]> queue = new ReferenceQueue<>()创建,里面的泛型写成和软引用里面
一样的泛型byte[],这个引用队列怎么配合刚才的软引用一起使用呢,我们在调用软引用的构造方法时,还可以给它多加一个
参数,就是引用队列对象,这样就关联了软引用对象和引用队列,关联了以后跟以前有什么不一样呢,当软引用所引用的byte[]
数组对象被回收时,那么软引用自身就会被加入到引用队列中去, 上面代码的前四个取值为null的、byte[]数组被回收的
软引用对象就会被加入到引用队列中去。等循环结束了,再真正去遍历软引用的时候,就可以先到queue里面找一找看看哪些
软引用它们已经被加入到queue中了,那么这些软引用就是没有用的了,就可以完成对软引用自身的回收。通过queue.poll()
去取得队列中那四个软引用对象,poll()方法就是从队列中获取最先放入队列的那个元素,把它移除队列,返回的结果是一个
Reference,Reference是SoftReference的父类型。如果poll != null,说明队列里还有内容,有内容的话就从list中
remove()掉这个软引用对象,每循环一次就把它remove()掉,直到队列为空说明队列中的元素已经被取完了,这样的话就把那
四个不需要的软引用对象从list集合中删除掉了。
等循环结束后,list中再遍历的时候就剩一个byte[]数组了,那四个null值被移除掉了,这样就把软引用自身所占用的内存也
释放掉了。

弱引用的案例

package Memory.GC;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

/**
 * 演示弱引用
 * -Xmx20m -XX:+PrintGCDetails -verbose:gc
 * 设置堆内存为20m
 */
public class Demo04 {
    private static 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 < 5; 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());
    }
}
当内存不足时,触发了垃圾回收,那么它就会尝试把WeakReference所引用的byte[]数组所占用的内存做一个相应的释放,
也是可以避免由于强引用导致的内存溢出问题。代码里面先创建了一个list,里面是一个WeakReference,再里面就是byte[]
数组类型,然后循环5次,每循环一次都创建一个新的WeakReference对象,里面包装了一个4MB的byte[]数组,把弱引用加入
到list当中。

在这里插入图片描述

第一次循环没有问题,第二次循环也没有问题,第三次循环还没有问题,第四次循环触发了一次垃圾回收,但是比较幸运,
这个时候弱引用所引用的对象还没有被回收掉,所以在垃圾回收后我们可以看到还有4个byte[]数组,之后再向其中放一
个byte[]数组,发现内存不够了,又触发了一次垃圾回收,这时候上一个(也就是第四个)byte[]数组对象被回收成null
了,这样第5个才能加进去,

将循环次数改为6次的结果

在这里插入图片描述

在这里插入图片描述

在放第10个时候,可能是由于弱引用本身也有一定的内存,虽然弱引用对应的byte[]数组是null,但是弱引用自身也要占用一点
内存,所以导致放第10个的时候放不下了,所以导致了一次Full GC,Full GC发生以后,发现前面9个弱引用对象它们所引用的
byte[]数组都被清掉了,在放置第9个byte[]时前3个还留着,现在放置第10个byte[]数组时Full GC发生了以后,前面三个也
被清光了,只有最后一个byte[]数组还留在了list当中。
通过上面的例子我们可以发现,弱引用一般都是在垃圾回收时就会把一些弱引用对象所占用的内存释放掉,当然,弱引用自身
它占用的内存要释放同样也要配合引用队列来实现,具体的做法跟软引用非常的类似,就不再演示了。
  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值