Java——》4种引用:强软弱虚

推荐链接:
    总结——》【Java】
    总结——》【Mysql】
    总结——》【Redis】
    总结——》【Kafka】
    总结——》【Spring】
    总结——》【SpringBoot】
    总结——》【MyBatis、MyBatis-Plus】
    总结——》【Linux】
    总结——》【MongoDB】
    总结——》【Elasticsearch】

Java——》4种引用:强软弱虚

  • 一、NormalReference = 强 = 普通 = 默认
  • 二、SoftReference = 软
  • 三、WeakReference = 弱
  • 四、PhantomReference = 虚
  • 五、四种引用的区别
    • 1、写法
    • 2、回收机制
    • 3、使用场景

引用的定义:
1.我们的数据类型必须是引用类型
2.我们这个类型的数据所存储的数据必须是另外一块内存的起始地址
Java——》4种引用:强软弱虚_第1张图片
Java——》4种引用:强软弱虚_第2张图片

一、NormalReference = 强 = 普通 = 默认

强引用是平常中使用最多的引用,强引用在程序内存不足(OOM)的时候也不会被回收

import java.io.IOException;

public class T01_NormalReference {
    public static void main(String[] args) throws IOException {
        // 强引用 = 普通引用 = 默认引用,只要有一个引用指向这个对象,那么GC一定不会回收它
        M m = new M();
        // 把m设置为null,就不会再有引用引用M这个对象,也就是说把m和new M()之间的引用给打断了,不再有关联了,这时候再运行程序 ,会发现M对象被GC回收了
        //m = null;
        System.gc(); //DisableExplicitGC,显示调用GC
        // 要在最后阻塞当前线程,因为GC是跑在别的线程的,如果main线程直接退出了,那GC就没什么意义了
        // 阻塞当前线程,就是让当前整个程序不会停止
        System.in.read();
    }

    static class M {
        @Override
        // GC的时候,会调用 finalize()
        // 这里重写finalize(),只是为了观察什么时候被GC,所以这个方法永远不需要重写,而且也不应该被重写
        protected void finalize() throws Throwable {
            System.out.println("finalize");
        }
    }
}

二、SoftReference = 软

软引用在程序内存不足时,会被回收

/**
 * 软引用
 * 软引用是用来描述一些还有用但并非必须的对象。
 * 对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。
 * 如果这次回收还没有足够的内存,才会抛出内存溢出异常。
 *
 * 当有一个对象,被软引用指向时,只有系统内存不够用时候,GC才会回收,内存够用GC不会回收
 *
 * 软引用非常适合缓存使用
 *
 * 注意:在程序运行前,设置一下堆内存最大为20M
 * 参数:-Xms20M -Xmx20M
 */
import java.lang.ref.SoftReference;

public class T02_SoftReference {
    public static void main(String[] args) {
        // 字节数组分配10M
        // 栈里面有个m,指向堆里的软引用SoftReference,软引用又指向了10M的字节数组
        SoftReference<byte[]> m = new SoftReference<>(new byte[1024*1024*10]);
        // m.get(),拿到字节数组,打印的是hashcode值
        System.out.println(m.get()); // 打印结果:[B@2344fc66
        System.gc();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 如果被回收,m.get() = null
        // 如果没回收,m.get() = 字节数组的hashcode值
        System.out.println(m.get());// 打印结果为:[B@2344fc66,虽然调用了GC,但是对象并没有被回收,因为堆内存够用

        //再分配一个数组15M,heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会把软引用干掉
        byte[] b = new byte[1024*1024*15];
        // 如果被回收,m.get() = null
        // 如果没回收,m.get() = 字节数组的hashcode值
        System.out.println(m.get());// 打印结果:null,因为堆空间不够用,GC把软引用给回收了

    }
}

三、WeakReference = 弱

弱引用就是只要JVM垃圾回收器发现了它,就会将之回收

/**
 * 弱引用遭到gc就会回收
 * 如果有一个强引用指向了这个弱引用之后,只要这个强引用消失了,这个弱引用就应该被回收,一般用在容器里,最典型的就是ThreadLocal

 * ThreadLocal tl = new ThreadLocal<>();
 * tl.set(new M());
 *
 * 1、哪里可以看出Entry的key是弱引用?
 * 往当前线程的threadLocals变量设置一个Entry,key是ThreadLocal对象,value是M对象
 * 由于Entry的父类是WeakReference,里面装的是ThreadLocal对象,调用了super(key),相当于new WeakReference(key),
 * 所以key是通过弱引用指向的ThreadLocal对象
 *
 * 2、为什么Entry要使用弱引用?
 * 当前线程是我们的main线程,tl是强引用指向ThreadLocal对象,tl是个局部变量,方法结束它就消失了。
 * 如果这个ThreadLocal对象还被一个强引用的key指向的时候,那这个ThreadLocal对象就回收不了了。
 * 而且由于很多线程是长期存在的,这个Map就会长期存在,那这个ThreadLocal对象永远不会被消失,就可能会出现内存泄漏。
 *
 * 但如果这个key是弱引用的话,就不会存在内存泄漏的问题,只要这个强引用消失了,这个弱引用就被回收了。
 *
 * 3、ThreadLocal为什么会出现内存泄漏?
 * tl是强引用指向ThreadLocal对象,tl是个局部变量,方法结束它就消失了。
 * 由于key是通过弱引用指向ThreadLocal对象,这时候key的指向也被回收了,key变成了null。
 * 由于这个threadLocals的Map是一直存在的,但key变成null了,那value就永远访问不到了,
 * 如果这个Map积累的越来越多,它还是会内存泄漏,所以ThreadLocal里面的对象不用了,一定要remove掉,不然还是会有内存泄漏。
 */

import java.lang.ref.WeakReference;

public class T03_WeakReference {
    public static void main(String[] args) {

        WeakReference<M> m = new WeakReference<>(new M());
        // 打印结果:com.mashibing.juc.c_022_RefTypeAndThreadLocal.T03_WeakReference$M@458ad742
        System.out.println(m.get());
        System.gc();
        // 打印结果:null,因为弱引用一定会被GC回收
        System.out.println(m.get());

        // tl是通过强引用指向的ThreadLocal对象
        ThreadLocal<M> tl = new ThreadLocal<>();
        // key是通过弱引用指向的ThreadLocal对象
        tl.set(new M());
        // 一定要remove,不然会发生内存泄漏
        tl.remove();
    }
    static class M {
        @Override
        // GC的时候,会调用 finalize()
        // 这里重写finalize(),只是为了观察什么时候被GC,所以这个方法永远不需要重写,而且也不应该被重写
        protected void finalize() throws Throwable {
            System.out.println("finalize");
        }
    }
}


四、PhantomReference = 虚

虚引用的回收机制跟弱引用差不多,但是它被回收之前,会被放入 ReferenceQueue 中。注意哦,其它引用是被JVM回收后才被传入 ReferenceQueue 中的。由于这个机制,所以虚引用大多被用于引用销毁前的处理工作。还有就是,虚引用创建的时候,必须带有 ReferenceQueue

/**
 *
 *
 *     一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,
 *     也无法通过虚引用来获取一个对象的实例。
 *     为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
 *     虚引用和弱引用对关联对象的回收都不会产生影响,如果只有虚引用活着弱引用关联着对象,
 *     那么这个对象就会被回收。它们的不同之处在于弱引用的get方法,虚引用的get方法始终返回null,
 *     弱引用可以使用ReferenceQueue,虚引用必须配合ReferenceQueue使用。
 *
 *     jdk中直接内存的回收就用到虚引用,由于jvm自动内存管理的范围是堆内存,
 *     而直接内存是在堆内存之外(其实是内存映射文件,自行去理解虚拟内存空间的相关概念),
 *     所以直接内存的分配和回收都是有Unsafe类去操作,java在申请一块直接内存之后,
 *     会在堆内存分配一个对象保存这个堆外内存的引用,
 *     这个对象被垃圾收集器管理,一旦这个对象被回收,
 *     相应的用户线程会收到通知并对直接内存进行清理工作。
 *
 *     事实上,虚引用有一个很重要的用途就是用来做堆外内存的释放,
 *     DirectByteBuffer就是通过虚引用来实现堆外内存的释放的。
 *
 */
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.LinkedList;
import java.util.List;

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();
                }
                // 打印的结果:null,虚引用是get不到值,但弱引用是可以get到值的
                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.printStackTrace();
        }

    }
}

五、四种引用的区别

1、写法

引用 写法
M m = new M();
SoftReference m = new SoftReference<>(new byte[1024102410]);
WeakReference m = new WeakReference<>(new M());
方式一:
private static final ReferenceQueue QUEUE = new ReferenceQueue<>();
PhantomReference phantomReference = new PhantomReference<>(new M(), QUEUE);
方式二:
PhantomReference prf = new PhantomReference(new M(),new ReferenceQueue<>());

2、回收机制

引用 回收机制
只要有一个引用指向这个对象,那么GC一定不会回收它
只有系统内存不够用的时候,GC才会回收这个对象,内存够用GC不会回收。
只要GC,就会回收
只要GC,就被回收,这个虚引用会装到这个队列Queue里,然后接收到一个通知,一般监听GC回收阶段,或者是回收堆外内存时使用。

3、使用场景

引用 使用场景
平常中使用最多
创建缓存的时候,创建的对象放进缓存中,当内存不足时,JVM就会回收早先创建的对象。
Java源码中的 java.util.WeakHashMap 中的 key 就是使用弱引用,一旦我不需要某个引用,JVM会自动帮我处理它,这样我就不需要做其它操作。可以解决内存泄漏的问题。
用于引用销毁前的处理工作,比如说资源释放或者回收堆外内存等。 Object.finalize() 虽然也可以做这类动作,但是这个方式即不安全又低效。

你可能感兴趣的:(Java,java,引用,GC,回收)