Java之引用(Reference)

Java之引用(Reference)JVM负责内存的分配和回收,java内存管理分为内存分配和内存回收,垃圾回收的机制主要是看对象是否有引用指向该对象。引用,一方面让程序员通过代码的方式决定某些对象的,另一方面有利于JVM进行垃圾回收。

何为引用

​ Java中是JVM负责内存的分配和回收,这是它的优点(使用方便,程序不用再像使用c那样操心内存),但同时也是它的缺点(不够灵活)。为了解决内存操作不灵活这个问题,可以采用软引用等方法。

JDK 1.2 版之后引入了软(SoftReference)、弱(WeakReference)、虚(PhantomReference)三种引用。

  1. 强引用:最传统的「引用」的定义,是指在程序代码之中普遍存在的引用赋值,即类似Object obj=new Object()这种引用关系。只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
  2. 软引用:描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
  3. 弱引用:描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
  4. 虚引用:是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。

总结:

  • 强 引用: 最普通的引用 Object o = new Object()
  • 软 引用: 垃圾回收器, 内存不够的时候回收 (缓存)
  • 弱 引用: 垃圾回收器看见就会回收 (防止内存泄漏) 应用实例:WeakHashMap、ThreadLocal
  • 虚 引用: 垃圾回收器看见二话不说就回收,跟没有一样 (管理堆外内存)DirectByteBuffer -> 应用到NIO Netty

用一句话形象的说明:

​ JVM就像一个国家,,GC就是城管,强引用就是当地人,软引用就是移民的人,弱引用就是黑户口,哪天城管逮到就遣走,虚引用就是一个带病的黑户口,指不定哪天自己就挂了

1.强引用(FinalReference)

public class Entity {
    // 该对象被GC回收的时候,会调用finalize方法
    // 生产中不建议重写此方法,因为不知道他什么时候会被调用,有时候不一定会调用
    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize");
        super.finalize();
    }
}
/** * 强引用 */
public class FinalReference01 {

    public static void main(String[] args) throws IOException {
        Entity entity = new Entity();
        entity = null;
        System.gc();// 禁用显式 GC(DisableExplicitGC)
        
        System.out.println(entity);
        // 阻塞main线程,给垃圾回收线程时间执行
        System.in.read();
    }
}
运行结果:
  finalize
  null

​ 强引用是使用最普遍的引用。正常引用,但没有人指向的时候就会被回收。如果一个对象具有强引用,那垃圾收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfM moryError错误,使程序异常终止,也不会靠随意回收具有强引用 对象来解决内存不足的问题。

2.软引用(SoftReference)

前提设置 VM参数:-Xmx20m -XX:+PrintGC

/** * 软引用何时被收集 * 运行参数 -Xmx20m -XX:+PrintGC * 适合做缓存 */
public class SoftReference02 {

    public static void main(String[] args) throws IOException, InterruptedException {
        //10M的缓存数据
        byte[] cacheData = new byte[10 * 1024 * 1024];
        //将缓存数据用软引用持有
        SoftReference<byte[]> softReference = new SoftReference<>(cacheData);
        // softReference强引用指向new SoftReference();,new SoftReference()软引用指向cacheData 10M的缓存

        //将缓存数据的强引用去除
        cacheData = null;
        System.out.println("第一次GC前" + cacheData);
        // 输出softReference的引用对象。
        // 如果此引用对象已被程序或垃圾收集器清除,则此方法返回 null
        System.out.println("第一次GC前" +softReference.get());


        //进行一次GC后查看对象的回收情况
        System.gc();
        //等待GC
        Thread.sleep(500);

        System.out.println("第一次GC后" + cacheData);
        System.out.println("第一次GC后" + softReference.get());

        //再分配一个数组,heap(堆)内存将装不下,这时系统会进行垃圾回收(GC),先回收一次,如果不够,会把软引用进行回收
        byte[] byteArr = new byte[12 * 1024 * 1024];
        System.out.println("分配后" + cacheData);
        System.out.println("分配后" + softReference.get());
    }
}
测试结果:
第一次GC前null
第一次GC前[B@7adf9f5f
[GC (System.gc())  12677K->11117K(19968K), 0.0014362 secs]
[Full GC (System.gc())  11117K->11074K(19968K), 0.0068026 secs]
第一次GC后null
第一次GC后[B@7adf9f5f
[GC (Allocation Failure)  11299K->11106K(19968K), 0.0014352 secs]
[GC (Allocation Failure)  11106K->11138K(19968K), 0.0007573 secs]
[Full GC (Allocation Failure)  11138K->11007K(19968K), 0.0117733 secs]
[GC (Allocation Failure)  11007K->11007K(19968K), 0.0034667 secs]
[Full GC (Allocation Failure)  11007K->749K(16384K), 0.0206738 secs]
分配后null
分配后null

​ 从上面的示例中就能看出,软引用关联的对象不会被GC回收。JVM在分配空间时,若果Heap空间不足,就会进行相应的GC,但是这次GC并不会收集软引用关联的对象,但是在JVM发现就算进行了一次回收后还是不足(Allocation Failure),JVM会尝试第二次GC,回收软引用关联的对象。

​ 像这种如果内存充足,GC时就保留,内存不够,GC再来收集的功能很适合用在缓存的引用场景中。在使用缓存时有一个原则,如果缓存中有就从缓存获取,如果没有就从数据库中获取,缓存的存在是为了加快计算速度,如果因为缓存导致了内存不足进而整个程序崩溃,那就得不偿失了。

​ 软引用是用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。

软引用: 垃圾回收器, 内存不够的时候回收 (缓存)

3.弱引用(WeakReference)

/** * 弱引用、 */
public class WeakReference03 {

    public static void main(String[] args) throws IOException, InterruptedException {
        //10M的缓存数据
        byte[] cacheData = new byte[10 * 1024 * 1024];
        //将缓存数据用软引用持有
         //weakReference强引用指向new WeakReference();,new WeakReference()弱引用指向cacheData 10M的缓存
        WeakReference<byte[]> weakReference = new WeakReference<>(cacheData);

        System.out.println("第一次GC前" + cacheData);
        System.out.println("第一次GC前" + weakReference.get());

        //进行一次GC后查看对象的回收情况
        System.gc();
        //等待GC
        Thread.sleep(500);

        System.out.println("第一次GC后" + cacheData);
        System.out.println("第一次GC后" + weakReference.get());

        //将缓存数据的强引用去除
        cacheData = null;
        System.gc();
        //等待GC
        Thread.sleep(500);

        System.out.println("第二次GC后" + cacheData);
        System.out.println("第二次GC后" + weakReference.get());
    }
}
执行结果:
第一次GC前[B@7adf9f5f
第一次GC前[B@7adf9f5f
第一次GC后[B@7adf9f5f
第一次GC后[B@7adf9f5f
第二次GC后null
第二次GC后null    

​ 弱引用也是用来描述非必须对象的,他的强度比软引用更弱一些,被弱引用关联的对象,在垃圾回收时,如果这个对象只被弱引用关联(没有任何强引用关联他),那么这个对象就会被回收。

弱 引用: 垃圾回收器看见就会回收 (防止内存泄漏) 应用实例:WeakHashMap、ThreadLocal

4.虚引用(PhantomReference)

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 PhantomReference04 {

    private static final List<Object> LIST = new LinkedList<>();
    private static final ReferenceQueue<Entity> QUEUE = new ReferenceQueue<>();

    public static void main(String[] args) {
        // //phantomReference强引用指向new PhantomReference();,new PhantomReference()虚引用指向new Entity()
        PhantomReference<Entity> phantomReference = new PhantomReference<>(new Entity(), QUEUE);

        System.out.println(phantomReference.get());

        new Thread(() -> {
            while (true) {
                LIST.add(new byte[1024 * 1024]);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // 结束当前线程
                    Thread.currentThread().interrupt();
                }
                System.out.println(phantomReference.get());
            }
        }).start();

        // 垃圾回收线程
        /** * 每次进行垃圾回收时,观察队列中是否存在虚引用回收的记录, * 如果队列中存在虚引用回收的记录,获取该虚引用的对象,观察是否指向堆外内存, * 如果指向堆外内存,JVM负责垃圾回收 * * 类似于:垃圾回收器会观察该此队列,队列中存在数据,说明,有虚引用指向的对象被回收了, * JVM 对队列中的对象进行特殊处理(是否指向堆外内存(该区域不归属GC管理),若指向则,进行对应的垃圾回收) */
        new Thread(() -> {
            while (true) {
                Reference<? extends Entity> poll = QUEUE.poll();
                if (poll != null) {
                    System.out.println("--虚引用对用被jvm回收了--" + poll);
                }
            }
        }).start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

​ 一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。虚引用和弱引用对关联对象的回收都不会产生影响,如果只有虚引用活着弱引用关联着对象,那么这个对象就会被回收。它们的不同之处在于弱引用的get方法,虚引用的get方法始终返回null,弱引用可以使用ReferenceQueue,虚引用必须配合ReferenceQueue使用。被标为虚引用的对象,在发生GC时会被放进队列,然后回收。

​ jdk中直接内存的回收就用到虚引用,由于jvm自动内存管理的范围是堆内存,而直接内存是在堆内存之外(其实是内存映射文件,自行去理解虚拟内存空间的相关概念),所以直接内存的分配和回收都是有Unsafe类去操作,java`在申请一块直接内存之后,会在堆内存分配一个对象保存这个堆外内存的引用,这个对象被垃圾收集器管理,一旦这个对象被回收,相应的用户线程会收到通知并对直接内存进行清理工作。

​ 和上面说的几个引用比,没发现有什么特别用处,你可以用来记录跟踪对象被垃圾回收的活动。有人形容虚引用为 —— “死亡证明,一般活人是根本不用的,只有人挂了的时候,去火葬场才能查到是哪个人挂了”

虚引用的应用主要在于管理直接内存

GC回收堆内存

JVM 回收堆外内存

总结

引用类型 被回收时间 用途 生存时间
强引用 从来不会 对象的一般状态 JVM停止运行时
软引用 内存不足时 对象缓存 内存不足时
弱引用 jvm垃圾回收时 对象缓存 gc运行后
虚引用 未知 未知 未知

参考

  1. 软引用、弱引用、虚引用-他们的特点及应用场景
  2. Java 关于强引用,软引用,弱引用和虚引用的区别与用法
  3. [ Java ] 超级大白话解释 —— 强引用、弱引用、软引用、虚引用(59.99秒懂)

今天的文章Java之引用(Reference)分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/18355.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注