提升--11---java的四种引用:强软弱虚

Posted 高高for 循环

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了提升--11---java的四种引用:强软弱虚相关的知识,希望对你有一定的参考价值。


java的四种引用:强软弱虚

什么是一个引用?

Object o = new Object()这就是一个引用

Object o = new Object()这就是一个引用了,一个变量指向new出来的对象,这就叫以个引用,引用这个东西,在java里面分4种,普通的引用比如Object o = new Object(),这个就叫强引用

1. 强引用

只要有一个应用指向这个对象,那么垃圾回收器一定不会回收它,这就是普通的引用,也就是强引用,

public class M {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize");
    }
}

  • 首先看到我们有一个类叫M,在这个类里我重写了一个方法叫finalize(),我们可以看到这个方法是已经被废弃的方法,为什么要重写他呢?主要想说明一下在垃圾回收的过程中,各种引用它不同的表现,垃圾回收的时候,它是会调用finalize()这个方法的,什么意思?
  • 当我们new出来一个象,在java语言里是不需要手动回收的,C和C++是需要的,在这种情况下,java的垃圾回收机制会自动的帮你回收这个对象,但是它回收对象的时候它会调用finalize()这个方法,我们重写这个方法之后我们能观察出来,它什么时候被垃圾回收了,什么时候被调用了,我在这里重写这个方法的含义是为了以后面试的时候方便你们造火箭,让你们观察结果用的,并不说以后在什么情况下需要重写这个方法,这个方法永远都不需要重写,而且也不应该被重写。
  • 我们来解释一下普通的引用NormalReference,普通的引用也就是默认的引用,默认的引用就是说,只要有一个应用指向这个对象,那么垃圾回收器一定不会回收它,这就是普通的引用,也就是强引用,为什么不会回收?因为有引用指向,所以不会回收,只有没有引用指向的时候才会回收,指向谁?指向你创建的那个对象。

2. 软引用

我们来说一下软引用的含义,当有一个对象(字节数组)被一个软引用所指向的时候,只有系统内存不够用的时候,才会回收它(字节数组)

//软引用非常适合缓存使用
public class T02_SoftReference {
    public static void main(String[] args) {
        SoftReference<byte[]> m = new SoftReference<>(new byte[1024*1024*10]);
        //m = null;
        System.out.println(m.get());
        System.gc();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(m.get());
        //再分配一个数组,heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会		 //把软引用干掉
        byte[] b = new byte[1024*1024*15];
        System.out.println(m.get());
    }
}

设置堆内存 -Xms20m -Xmx20m


分析;

  • 我们来跑一下这个程序,在程序运行的时候,我们来设置一下堆内存最大为20MB,就是说我堆内存直接给你分配20MB,你要设置一下堆内存,如果不设置它永远不会回收的,这个时候我们运行程序你会发现,第三次调用m.get()输出的时候,输出的值为null,我们来分析一下,第一次我们的堆内存这个时候最多只能放20MB,第一次创建字节数组的时候分配了10MB,这个时候堆内存是能分配下的,这个时候我调用了gc来做回收是无法回收的,因为堆内存够用,第二次创建字节数组的时候分配了15MB,这个时候对内存的内存还够15MB吗?肯定是不够的,不够了怎么办?清理,清理的时候既然内存不够用,就会把你这个软引用给干掉,然后15MB内存分配进去,所以这个时候你再去get第一个字节数组的时候它是一个null值,这是就是软引用的含义,用大腿想一想这个软引用的使用场景:做缓存用,这个东西主要做缓存用

应用:

软引用的使用场景:做缓存用,这个东西主要做缓存用

  • 举个例子你从内存里边读一个大图片,特别的图片出来,然你用完了之后就没什么用了,你可以放在内存里边缓存在那里,要用的时候直接从内存里边拿,但是由于这个大图片占的空间比较大,如果不用的话,那别人也要用这块空间,那就把它干掉,这个时候就用到了软引用
  • 再举个例子,从数据库里读一大堆的数据出来,这个数据有可能比如说你按一下back,我还可以访问到这些数据,如果内存里边有的话,我就不用从数据库里拿了,这个时候我也可以用软应用,需要新的空间你可以把我干掉,没问题我下次去数据库取就行了,但是新空间还够用的时候,我下次就不用从数据库取,直接从内存里拿就行了

3. 弱引用

弱引用的意思是,只要遭遇到gc就会回收

  • 接下来我们来说一下弱引用,弱引用的意思是,只要遭遇到gc就会回收,刚才我们说到软引用的概念是,垃圾回收不一定回收它,只有空间不够了才会回收它,所以软引用的生存周期还是比较长的,我们接着说弱应用,弱引用就是说,只要垃圾回收看到这个引用是一个特别弱的引用指向的时候,就直接把它给干掉
import java.lang.ref.WeakReference;

public class T03_WeakReference {
    public static void main(String[] args) {
        WeakReference<M> m = new WeakReference<>(new M());
        System.out.println(m.get());
        System.gc();
        System.out.println(m.get());
        ThreadLocal<M> tl = new ThreadLocal<>();
        tl.set(new M());
        tl.remove();
    }
}


class M {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize");
    }
}

  • 运行程序以后我们看到,第一次打印出来了,第二次打印之前调用了gc,所以第二次打印出了null值,那我们想这东西本来指向一个弱引用对象,小m指向这个弱引用对象,这个弱引用对象里边有一个弱弱的引用指向了另外一个大M对象,但这个大M对象垃圾回收一来就把它干掉了,
  • 那么把它创建出来有什么用呢?这个东西作用就在于,如果有另外一个强引用指向了这个弱引用之后,只要这个强引用消失掉,这个弱引用就应该去被回收,我就不用管了,只要这个强引用消失掉,我就不用管这个弱引用了,这个弱引用也一定是被回收了,这个东西用在什么地方呢?一般用在容器里

最典型的一个应用ThreadLocal

4. 虚引用

随时可能被回收,创建了可能很快就会被回收

对于虚引用它就干一件事,它就是管理堆外内存的,

  • 首先第一点,这个虚引用的构造方法至少都是两个参数的,第二个参数还必须是一个队列,这个虚引用基本没用,就是说不是给你用的,那么它是给谁用的呢?是给写JVM(虚拟机)的人用的
public class T04_PhantomReference {
    private static final List<Object> LIST = new LinkedList<>();
    private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>();
    public static void main(String[] args) {
        PhantomReference<M> phantomReference = new PhantomReference<>(new M(), QUEUE);
        new Thread(() -> {
            while (true) {
                LIST.add(new byte[1024 * 1024]);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();
                }
                System.out.println(phantomReference.get());
            }
        }).start();

        new Thread(() -> {
            while (true) {
                Reference<? extends M> poll = QUEUE.poll();
                if (poll != null) {
                    System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll);
                }
            }
        }).start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printSackTrace();
        }
    }
}

  • 我们来看下面的小程序,在小程序里创建了一个List集合用于模拟内存溢出,还创建了一个ReferenceQueue(引用队列),在main方法里创建一个虚引用对象PhantomReference,这个虚引用对象指向的这个内存里是什么样子的呢?有一个phantomReference对象指向了一个new出来的PhantomReference对象,这个对像里面可以访问两个内容,第一个内容是它又通过一个特别虚的引用指向了我们new出来的一个M对象,第二个内容它关联了一个Queue(队列),这个时候一但虚引用被回收,这个虚引用会装到这个队列里,也就是说这个队列是干什么的呢?就是垃圾回收的时候,一但把这个虚引用给回收的时候,会装到这个队列里,让你接收到一个通知,什么时候你检测到这个队列里面如果有一个引用存在了,那说明什么呢?说明这个虚引用被回收了,这个虚引用叫特别虚的引用,指向的任何一个对象,垃圾回收二话不说,上来就把这个M对象给干掉这是肯定的,只要有垃圾回收,而且虚引用最关键的是当M对象被干掉的时候,你会收到一个通知,通知你的方式是什么呢?通知你的方式就是往这个Queue(队列)里放进一个值
  • 那么我们这个小程序是什么意思呢?在小程序启动前先设置好了堆内存的最大值,然后看第一个线程启动以后,它会不停的往List集合里分配对象,什么时候内存占满了,触发垃圾回收的时候,另外一个线程就不断的监测这个队列里边的变动,如果有就说明这个虚引用被放进去了,就说明被回收了
  • 在第一个线程启动后我们会看到,无论我们怎么get这个phantomReference里面的值,它输出的都是空值,虚引用和弱引用的区别就在于,弱引用里边有值你get的时候还是get的到的,但是虚引用你get里边的值你是get不到的

那么我们想一下你拿不到这里边的值我用它来干什么呢?这里再强调一遍,只是为了给你一个通知,通知的时候放到队列里,这虚引用干什么用?就是写JVM的人拿来用,写JVM的人用的时候怎么用呢?他会当Queue这个值,检测到队列里边有虚引用指向这个东西被回收的时候做出相应的处理,什么时候出现相应的处理呢?

  • 经常会有一种情况,NIO里边有一个比较新的新的Buffer叫DirectByteBuffer(直接内存),直接内存是不被JVM(虚拟机)直接管理的内存,被谁管理?被操作系统管理,又叫做堆外内存,这个DirectByteBuffer是可以指向堆外内存的,那我们想一下,如果这个DirectByteBuffer设为null,垃圾回收器能回收DirectByteBuffer吗?它指向内存都没在堆里,你怎么回收它,所以没有办法回收,那么写虚拟机的人怎么回收DirectByteBuffer呢?如果有一天你也用到堆外内存的时候,当这个DirectByteBuffer被设为null的时候,你怎么回收堆外这个内存呢?你可以用虚引用,当我们检测到这个虚引用被垃圾回收器回收的时候,你做出相应处理去回收堆外内存
  • 说不定将来的某一天,你写了一个Netty,然后你再Netty里边分配内存的时候,用的是堆外内存,那么堆外内存你又想做到自动的垃圾回收,你不能让人家用你API的人,让人家自己去回收对不对?所以你这个时候怎么做到自动回收呢?你可以检测虚引用里的Queue,什么时候Queue检测到DirectByteBuffer(直接内存)被回收了,这个时候你就去清理堆外内存,堆外内存怎么回收呢? 你如果是C和C++语言写的虚拟机的话,当然是del和free这个两个函数,它们也是C和C++提供的,java里面现在也提供了,堆外内存回收,这个回收的类叫Unsafe,这个类在JDK1.8的时候可以用java的反射机制来用它,但是JDK1.9以后它被加到包里了,普通人是用不了的,但JUC的一些底层有很多都用到了这个类,这个Unsafe类里面有两个方法,allocateMemory方法直接分配内存也就是分配堆外内存,freeMemory方法回收内存也就是手动回收内存,这和C/C++里边一样你直接分配内存,必须得手动回收

1.垃圾回收的时候,一但把这个虚引用给回收的时候,会装到这个队列里,让你接收到一个通知

2.当我们检测到这个虚引用被垃圾回收器回收的时候,你做出相应处理去回收堆外内存

以上是关于提升--11---java的四种引用:强软弱虚的主要内容,如果未能解决你的问题,请参考以下文章

JAVA不可不知的强软弱虚四种引用

《我要进大厂系列 七》-谈谈你对强软弱虚四种引用理解?

Java中的四种引用类型,强引用,软引用,弱引用,虚引用

Java中的四种引用(强引用软引用弱引用虚引用)

详解Java中对象的软弱和虚引用的区别

强软弱虚4类引用