Java中四种引用详解

目录
  • 强引用
  • 软引用
  • 弱引用
  • 虚引用
  • 总结

java 中的 4 种引用方式,适用于不同的场景,重点需要理解虚引用,结合文字和代码

强引用

被强引用的对象,不会被垃圾回收器回收,JVM 宁愿抛出 OOM 也不会去回收被强引用的对象;

M m = new M();

软引用

当堆空间够用时,GC 不会对软引用的对象进行回收,当堆空间不足以分配新的空间时,触发 GC 就会对这部分对象进行回收,通常用在缓存等领域。将缓存对象使用软引用,空间不足的时候释放这部分空间,需要再次使用的时候,重新从 DB 中加载即可。

另外软引用可以配合队列(ReferenceQueue) 来使用,如果软引用引用的对象被垃圾回收,JVM 会把软引用加入到与之关联的引用队列中。

/**
 * 软引用:一般用在缓存,只要空间不足,GC 跑起来就会回收它
 * 运行参数 -Xmx200m -XX:+PrintGC
 * Created by etfox on 2021/03/01 17:06
 **/
public class TestSoftReference {
    public static void main(String[] ags) throws InterruptedException {
        //100M的缓存数据
        byte[] cacheData = new byte[100 * 1024 * 1024];
        //将缓存数据用软引用持有
        SoftReference<byte[]> cacheRef = new SoftReference<>(cacheData);
        //将缓存数据的强引用去除
        cacheData = null;
        System.out.println("第一次GC前" + cacheData);
        System.out.println("第一次GC前" + cacheRef.get());
        //进行一次GC后查看对象的回收情况
        System.gc();
        //等待GC
        Thread.sleep(500);
        System.out.println("第一次GC后" + cacheData);
        System.out.println("第一次GC后" + cacheRef.get());
        //在分配一个120M的对象,看看缓存对象的回收情况
        byte[] newData = new byte[120 * 1024 * 1024];
        System.out.println("分配后" + cacheData);
        System.out.println("分配后" + cacheRef.get());
    }
}
console==>
[GC (Allocation Failure)  4120K->1055K(15872K), 0.0016237 secs]
[Full GC (Allocation Failure)  1055K->1054K(15872K), 0.0015426 secs]
第一次GC前null
第一次GC前[B@1973e9b
[Full GC (System.gc())  103583K->103455K(118340K), 0.0015559 secs]
第一次GC后null
第一次GC后[B@1973e9b
[GC (Allocation Failure)  105575K->103455K(198016K), 0.0001733 secs]
[Full GC (Allocation Failure)  103455K->103455K(198016K), 0.0011860 secs]
[Full GC (Allocation Failure)  103455K->819K(198016K), 0.0012080 secs]
分配后null
分配后null

弱引用

弱引用的引用对象在每次 GC 时,不管当前堆内存大小,都会将这个对象清除。如果此对象偶尔使用,并且希望需要用到的时候可以获取到,但是又不希望影响这个对象的回收,就可以使用弱引用来描述对象。

当然弱引用也可以结合事件队列使用。

/**
 * 弱引用:如果对象时偶尔使用,并且希望使用的时候就能获取到(get),但是又不想影响此对象的垃圾收集
 * 可以引入队列
 * Created by etfox on 2021/03/01 17:59
 **/
public class TestWeakReference {
    public static void main(String[] args) throws InterruptedException {
        //100M的缓存数据
        byte[] cacheData = new byte[100 * 1024 * 1024];
        //将缓存数据用软引用持有
        WeakReference<byte[]> cacheRef = new WeakReference<>(cacheData);
        System.out.println("第一次GC前" + cacheData);
        System.out.println("第一次GC前" + cacheRef.get());
        //进行一次GC后查看对象的回收情况
        System.gc();
        //等待GC
        Thread.sleep(500);
        System.out.println("第一次GC后" + cacheData);
        System.out.println("第一次GC后" + cacheRef.get());
        //将缓存数据的强引用去除
        cacheData = null;
        System.gc();
        //等待GC
        Thread.sleep(500);
        System.out.println("第二次GC后" + cacheData);
        System.out.println("第二次GC后" + cacheRef.get());
    }
}
console==>
[GC (Allocation Failure)  3912K->1025K(15872K), 0.0016372 secs]
[Full GC (Allocation Failure)  1025K->1024K(15872K), 0.0014157 secs]
第一次GC前[B@1973e9b
第一次GC前[B@1973e9b
[Full GC (System.gc())  103723K->103456K(118340K), 0.0016463 secs]
第一次GC后[B@1973e9b
第一次GC后[B@1973e9b
[Full GC (System.gc())  105601K->1056K(198016K), 0.0012771 secs]
第二次GC后null
第二次GC后null

虚引用

虚引用,顾名思义是虚幻的,虚引用的对象并不能在 get 的时候获取到它。它也在我们日常开发中没有适用的场景,它的主要作用是用来跟踪一个对象的生命周期 (通常来说是直接内存 [JDK1.5 Java 中除了由 JVM 管理的空间,还可以在内存中直接分配对象]中的对象),一般使用在 JVM 的开发中,主要用来管理直接内存,因为直接内存通常 GC 无法管理这一块内存(C++ delete 完事),需要特殊处理。

例如 NIO 的 ByteBuffer.allocateDirect(1024); 分配内存到直接内存空间中,通常来说从网卡中读取的数据,由操作系统读取到直接内存中,在需要使用的时候,需要拷贝到 JVM 堆空间中,如果不使用 allocateDirect 就需要一个拷贝的过程,这是非常消耗时间的,

// |-- ---| | --------| |------------|
// | 网卡 | ==> | 直接内存 | == copy ==> | JVM 堆空间 |
// |--- --| | ------- | |------------|

使用直接存内存省略了拷贝的过程,俗称 nio 的 zero copy,但是直接内存中的对象在不需要使用的时候无法通过正常 GC 过程去管理这一块空间,所以用到了虚引用,

解释:

虚引用需要配合一个事件队列一起使用,JVM GC 的时候并不是说把虚引用的引用清理掉完事,而是说会把虚引用的引用放到事件队列当中,垃圾回收线程会时不时的去检查这个事件队列,看一下引用的回收过程需不需要做一些后续善后处理(例如清理直接内存中的对象,这玩意儿由实现人去弄)这就是虚引用的作用和含义了。

/**
 * 虚引用:可以通过队列跟踪一个对象的生命周期,一般在写 JVM 相关的时候才会用到虚引用,主要用来管理直接内存(C++ delete 一下子完事)
 * -Xmx20m -XX:+PrintGC
 * Created by etfox on 2021/03/03 12:14
 **/
public class TestPhantomReference {
    private static final List<Object> LIST = new LinkedList<>();
    private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>();
    public static void main(String[] args) throws InterruptedException {
        final PhantomReference<M> phantomReference = new PhantomReference<>(new M(), QUEUE);
        // 永远都会返回 null
        System.out.println(phantomReference.get());
        // ByteBuffer.allocateDirect(1024); 分配内存到操作系统的空间,直接内存的空间, JDK 1.5
        // 通常从网卡读取的数据,通常由系统读取到直接内存里面,如果想用,需要拷贝到 JVM 堆空间里
        // 如果不使用 allocateDirect(直接内存) 就需要一个拷贝的过程,是非常消耗时间的
        // |-- ---|     | --------|             |------------|
        // | 网卡  | ==> | 直接内存 | == copy ==> |  JVM 堆空间 |
        // |--- --|     | ------- |             |------------|
        // 使用直接内存省略了拷贝的过程,俗称 nio zero copy
        // 但是直接内存中的对象在不再需要的时候无法由 JVM 去 GC 清理内存,所以用到了虚引用
        // 虚引用需要和一个队列一起使用,JVM GC 时并不是说会把虚引用的引用清理,而是说会把虚引用的引用放到事件队列中
        // 垃圾回收线程可以时不时的检查这个事件队列,看一下这个引用的回收过程需不需要做一些善后处理(例如清理直接内存的那个对象)
        // 这就是虚引用的作用和含义
        ByteBuffer b = ByteBuffer.allocateDirect(1024);
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    LIST.add(new byte[1024 * 1024]);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(phantomReference.get());
                }
            }
        }).start();
        // 模拟垃圾回收线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    Reference<? extends M> poll = QUEUE.poll();
                    if (poll != null) {
                        System.out.println("虚引用对象被 JVM 回收了 " + poll);
                    }
                }
            }
        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static class M {
        @Override
        protected void finalize() throws Throwable {
            System.out.println("当对象将被回收时, GC 会调用当前方法");
        }
    }
}
console==>
null
[GC (Allocation Failure)  3493K->1061K(15872K), 0.0017311 secs]
当对象将被回收时, GC 会调用当前方法
null
null
null
[GC (Allocation Failure)  4483K->4132K(15872K), 0.0020184 secs]
null
null
null
null
[GC (Allocation Failure)  8299K->8228K(15872K), 0.0017350 secs]
null
null
null
null
[GC (Allocation Failure)  12401K->12324K(17928K), 0.0016853 secs]
[Full GC (Allocation Failure)  12324K->12324K(17928K), 0.0011354 secs]
虚引用对象被 JVM 回收了 java.lang.ref.PhantomReference@d5fbc1
null
null
null
null
null
[Full GC (Allocation Failure)  17599K->17445K(19840K), 0.0019903 secs]
null
[Full GC (Allocation Failure)  18524K->18469K(19840K), 0.0011629 secs]
[Full GC (Allocation Failure)  18469K->18232K(19840K), 0.0022320 secs]
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
	at com.pangu.TestPhantomReference$1.run(TestPhantomReference.java:41)
	at java.lang.Thread.run(Thread.java:748)

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • 面试官:详细谈谈Java对象的4种引用方式

    前言 我们知道在Java中除了基础的数据类型以外,其它的都为引用类型. 而Java根据其生命周期的长短将引用类型又分为强引用.软引用.弱引用.幻象引用 . 正常情况下我们平时基本上我们只用到强引用类型,而其他的引用类型我们也就在面试中,或者平日阅读类库或其他框架源码的时候才能见到. 1.强引用 我们平日里面的用到的new了一个对象就是强引用,例如 Object obj = new Object();当JVM的内存空间不足时,宁愿抛出OutOfMemoryError使得程序异常终止也不愿意回收具有

  • 简述Java中的四种引用类型

    简介 从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期.这四种级别由高到低依次为:强引用.软引用.弱引用和虚引用,下面分别介绍下这四种引用. 强引用 强引用是最常用的引用类型,如下所示,new Object()会创建一个Object对象并存储在堆上,变量object存储对该对象的强引用. Object object = new Object(); 强引用不会被垃圾回收,所以要想回收该对象,则应该将指向该对象的变量显示设为null,这样该对象就由强引用转变

  • Java的四种引用方式

    目录 1.强引用(StrongReference) 2.软引用(SoftReference) 3.弱引用(WeakReference) 4.虚引用(PhantomReference) 5. 引用队列(ReferenceQueue) 1.强引用(StrongReference) 使用最普遍的引用. 只要引用链没有断开,强引用就不会断开.- 当内存空间不足,抛出OutOfMemoryError终止程序也不会回收具有强引用的对象. 通过将对象设置为null来弱化引用,使其被回收 Object obje

  • 详解Java的引用类型及使用场景

    每种编程语言都有自己操作内存中元素的方式,例如在 C 和 C++ 里是通过指针,而在 Java 中则是通过"引用".在 JDK.1.2 之后,Java 对引用的概念进行了扩充,将引用分为了:强引用(Strong Reference).软引用(Soft Reference).弱引用(Weak Reference).虚引用(Phantom Reference)4 种,这 4 种引用的强度依次减弱,今天这篇文章就简单介绍一下这四种类型,并简单说一下他们的使用场景. 1. 强引用(Strong

  • 四种引用类型在JAVA Springboot中的使用详解

    目录 概念介绍 01.  强引用 02.  软引用 03.  弱引用 04.  虚引用 对象可达性 Springboot源码中的使用 总结 概念介绍 不同的引用类型,主要体现的是对象不同的可达性(reachable)状态和对垃圾收集的影响. 01.  强引用 这个就是我们创建的普通对象了~ 当该对象被显示地赋值为 null 时,或者没有被其他存活的对象继续引用时,它就会成为垃圾收集器的目标,等待被收回 02.  软引用 软引用( SoftReference ) , 当内存不足 时会被回收 比如

  • Java中四种引用详解

    目录 强引用 软引用 弱引用 虚引用 总结 java 中的 4 种引用方式,适用于不同的场景,重点需要理解虚引用,结合文字和代码 强引用 被强引用的对象,不会被垃圾回收器回收,JVM 宁愿抛出 OOM 也不会去回收被强引用的对象: M m = new M(); 软引用 当堆空间够用时,GC 不会对软引用的对象进行回收,当堆空间不足以分配新的空间时,触发 GC 就会对这部分对象进行回收,通常用在缓存等领域.将缓存对象使用软引用,空间不足的时候释放这部分空间,需要再次使用的时候,重新从 DB 中加载

  • Java中遍历ConcurrentHashMap的四种方式详解

    这篇文章主要介绍了Java中遍历ConcurrentHashMap的四种方式详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 方式一:在for-each循环中使用entries来遍历 System.out.println("方式一:在for-each循环中使用entries来遍历");for (Map.Entry<String, String> entry: map.entrySet()) { System.out.pr

  • Java中四种线程池的使用示例详解

    在什么情况下使用线程池? 1.单个任务处理的时间比较短 2.将需处理的任务的数量大 使用线程池的好处: 1.减少在创建和销毁线程上所花的时间以及系统资源的开销 2.如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及"过度切换". 本文详细的给大家介绍了关于Java中四种线程池的使用,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: FixedThreadPool 由Executors的newFixedThreadPool方法创建.它是一种线程数量固定的线程

  • Java 不使用第三方变量交换两个变量值的四种方法详解

    目录 变量本身交换数值 算术运算 指针地址操作 位运算 简单总结 哈喽,大家好,我是阿Q.前几天有个小伙伴去面试,被面试官的一个问题劝退了:请说出几种不使用第三方变量交换两个变量值的方法. 问题有点绕,好不容易缕清了面试官的问题,却发现答不上来.一时间尴尬无比,只能硬着头皮说不会. 遇到交换变量值的问题,通常我们的做法是:定义一个新的变量,借助它完成交换. 代码如下: t = a; a = b; b = t; 但问题的重点是"不使用第三方变量",那就变得"可爱"起来

  • Javaweb中Request获取表单数据的四种方法详解

    目录 表单代码 request.getParamter(String name);通过name获取值 request.getParamterValues(String name);通过name获取value值(一般用于复选框获取值) 代码片段 request.getParameterNames();直接获取表单所有对象的name值,返回值是枚举集合 request.getParameterMap();直接获取表单所有对象的name值以及数据 表单代码 <!DOCTYPE html> <h

  • JS中不应该使用箭头函数的四种情况详解

    目录 箭头函数的一些缺点 1.不支持参数对象 2.无法通过apply.call.bind来改变this指针 什么时候不能使用箭头功能 1.请不要在构造函数中使用箭头函数 2.请不要在点击事件中操作this 3.请不要在对象的方法中使用箭头函数. 4.请不要在原型链中使用箭头函数 箭头函数给我们的工作带来了极大的方便,但是它们有什么缺点呢?我们应该一直使用箭头函数吗?我们应该在哪些场景中停止使用箭头函数? 现在,我们开始吧. 箭头函数的一些缺点 1.不支持参数对象 在箭头函数中,我们不能像在普通函

  • Java中的反射机制详解

    Java中的反射机制详解 反射,当时经常听他们说,自己也看过一些资料,也可能在设计模式中使用过,但是感觉对它没有一个较深入的了解,这次重新学习了一下,感觉还行吧! 一,先看一下反射的概念: 主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义. 反射是Java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接.但是反射使用不当会成本很高! 看概念很晕的,继续往下

  • 浅谈JAVA中输入输出流实例详解

    java语言的输入输出功能是十分强大而灵活的,美中不足的是看上去输入输出的代码并不是很简洁,因为你往往需要包装许多不同的对象.在Java类库中,IO部分的内容是很庞大的,因为它涉及的领域很广泛:标准输入输出,文件的操作,网络上的数据流,字符串流,对象流,zip文件流....本文的目的是为大家介绍JAVA中输入输出流实例详解. 流的层次结构 定义:        java将读取数据对象成为输入流,能向其写入的对象叫输出流.结构图如下: 1.输入输出: 输入/输出(Input/Output)是指对某

  • 基于java中集合的概念(详解)

    1.集合是储存对象的,长度可变,可以封装不同的对象 2.迭代器: 其实就是取出元素的方式(只能判断,取出,移除,无法增加) 就是把取出方式定义在集合内部,这样取出方式就可以直接访问集合内部的元素,那么取出方式就被定义成了内部类. 二每一个容器的数据结构不同,所以取出的动作细节也不一样.但是都有共性内容判断和取出,那么可以将共性提取,这些内部类都符合一个规则Iterator Iterator it = list.iterator(); while(it.hasNext()){ System.out

  • java中变量和常量详解

    变量和常量 在程序中存在大量的数据来代表程序的状态,其中有些数据在程序的运行过程中值会发生改变,有些数据在程序运行过程中值不能发生改变,这些数据在程序中分别被叫做变量和常量. 在实际的程序中,可以根据数据在程序运行中是否发生改变,来选择应该是使用变量代表还是常量代表. 变量 变量代表程序的状态.程序通过改变变量的值来改变整个程序的状态,或者说得更大一些,也就是实现程序的功能逻辑. 为了方便的引用变量的值,在程序中需要为变量设定一个名称,这就是变量名.例如在2D游戏程序中,需要代表人物的位置,则需

随机推荐