Java中的四种引用类型及其使用方式

这篇具有很好参考价值的文章主要介绍了Java中的四种引用类型及其使用方式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Java中有四种引用类型,分别是强引用(Strong Reference)、软引用(Soft Reference)、弱引用(WeakReference)、虚引用(PhantomReference)。

为什么要将引用分成这四种类型?

这要从Java管理内存的方式说起。Java为了将程序员从内存管理中解救出来,即不让程序员自己申请堆内存(比如C语言程序员需要通过malloc请求操作系统分配一块堆内存给自己使用),自己去释放堆内存(比如C语言程序员需要通过free来释放内存),降低程序员的心智负担,且可以增加开发效率。Java虚拟机通过追踪Java中的对象是否有来自GC Roots的强引用指向它来决定对象所占用的堆内存是否可以回收。

强引用

我们平常写的代码,比如:

String abc = new String();
List<Integer> intList = new ArrayList<>();

其中的引用abc和intList都是强引用,也就是说引用默认都是强引用。

软引用

软引用是什么样的呢?如果我们想用软引用,需要通过java.lang.ref.SoftReference,比如:

SoftReference<YourObject> softRef = new SoftReference<>(new YourObject());

这样,softRef这个强引用指向的SoftReference对象中保存的就是一个软引用,这儿就是YourObject对象的软引用。如图:

Java中的四种引用类型及其使用方式

如果我们想要获取软引用指向的对象,可以通过SoftReference对象的get方法获取,比如:

YourObject yourObj = softRef.get();

注意,软引用是为了告诉Java虚拟机,如果内存不足的时候,你可以把我指向的对象回收掉。也就是说,即使SoftReference对象仍然可以通过GC Roots访问到,但是它内部的软引用指向的对象仍然可以被回收。

我们举个例子:

package references;

import java.lang.ref.SoftReference;

/**
 * @author pilaf
 * @description
 * @date 2023-04-05 21:03
 **/
public class SoftReferenceDemo {

    public static class DataBlock {
        private final byte[] bytes;

        public DataBlock(int byteCount) {
            // 创建DataBlock对象的时候,将会在堆上分配一个byteCount个字节的数组
            bytes = new byte[byteCount];
        }

        public String toString() {
            return "DataBlock(byteCount=" + bytes.length + ")";
        }
    }

    public static void main(String[] args) {
        // 1024*1024*10字节是10MB
        SoftReference<DataBlock> softReference = new SoftReference<>(new DataBlock(1024 * 1024 * 10));
        System.out.println("通过软引用访问到的对象:" + softReference.get());
        System.out.println("通过软引用访问到的对象:" + softReference.get());
    }
}

通过配置JVM运行参数,指定-Xms10m -Xmx10m,我们让堆大小为10MB:

Java中的四种引用类型及其使用方式

运行上面的代码,控制台输出:

Java中的四种引用类型及其使用方式

可见,可以通过软引用访问到我们的DataBlock对象。

我们还可以再两次调用的System.out.println(“通过软引用访问到的对象:” + softReference.get());中间加上:

System.gc(); 

会发现第二次仍然可以拿到软引用指向的对象:

Java中的四种引用类型及其使用方式

让我们再做点小改动,将main方法的方法体改成下面:

        // 1024*1024*10字节是10MB
        SoftReference<DataBlock> softReference = new SoftReference<>(new DataBlock(1024 * 1024 * 10));
        System.out.println("通过软引用访问到的对象:" + softReference.get());
        // 创建一个10MB的数组
        byte[] anotherByteArray = new byte[1024 * 1024 * 10];
        System.out.println("通过软引用访问到的对象:" + softReference.get());

即增加了一行创建anotherByteArray数组的代码,再次运行main方法:

Java中的四种引用类型及其使用方式

看到了么,当我们尝试再在堆上分配一个10MB的数组的时候,堆内存会不够用(因为我们前面配置了堆内存为10MB),这个时候JVM会回收掉软引用指向的对象,当我们再次调用softReference.get()来获取softReference对象内部保存的软引用指向的对象的时候,发现它已经是null了,即被回收了。

弱引用

弱引用和软引用的用法一样,只不过我们需要通过java.lang.ref.WeakReference对象来包着一个弱引用。但是弱引用的更脆弱,只要垃圾回收器回收内存垃圾的时候,不管内存是否够用,都会回收掉弱引用指向的对象,比如下面的程序:

package references;

import java.lang.ref.WeakReference;

/**
 * @author pilaf
 * @description
 * @date 2023-04-05 21:41
 **/
public class WeakReferenceDemo {
    
    public static void main(String[] args) {

        WeakReference<String> weakReference = new WeakReference<>(new String("abc"));
        System.out.println("通过弱引用访问到的对象:" + weakReference.get());
        System.gc(); // 触发垃圾回收
        System.out.println("通过弱引用访问到的对象:" + weakReference.get());
    }
}

运行的结果:

Java中的四种引用类型及其使用方式

可见,只要经过垃圾回收,弱引用指向的对象都会被回收,不管堆内存是否够用。

JDK中的ThreadLocal内部的ThreadLocalMap就用到了弱引用来避免内存泄漏:

Java中的四种引用类型及其使用方式

虚引用

虚引用主要用于在垃圾回收器回收完虚引用指向的对象后,允许我们做一些资源释放的工作(比如释放一些堆外内存)。

让我们先模仿着前面的软引用和弱引用的方式使用虚引用:

package references;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author pilaf
 * @description
 * @date 2023-04-05 22:00
 **/
public class PhantomReferenceDemo {

    private static class MyClass{
        private Date birthTime;
        public MyClass(){
            birthTime = new Date();
        }
    }

    public static void main(String[] args) {
        // 如果PhantomReference构造器的第二个参数java.lang.ref.ReferenceQueue传递null,那么永远也无法获取到虚引用指向的对象了
        PhantomReference<MyClass> phantomReference = new PhantomReference<>(new MyClass(), null);
        // PhantomReference的get方法总是返回null,因为虚引用指向的对象总是无法访问的。
        System.out.println("phantomReference.get:"+phantomReference.get());

    }
}

运行后控制台输出:

phantomReference.get:null

因为虚引用的get方法永远返回null。

我们应该通过下面这样的方式使用虚引用:

package references;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author pilaf
 * @description
 * @date 2023-04-05 22:00
 **/
public class PhantomReferenceDemo {

    private static class MyClass{
        private Date birthTime;
        public MyClass(){
            birthTime = new Date();
        }
    }

    public static void main(String[] args) throws Exception{
        ReferenceQueue<MyClass> queue = new ReferenceQueue<>();
        // 虚引用需要和引用队列一起使用,这样再垃圾回收完虚引用的对象后,它的虚引用会被放到队列中
        PhantomReference<MyClass> phantomReferenceWithQueue = new PhantomReference<>(new MyClass(), queue);

        // 启动另一个线程来检查是否有虚引用被回收了
        new Thread(()->{
            while (true){
                Reference<? extends MyClass> ref = queue.poll();
                if (ref!=null){
                    System.out.println("虚引用被回收:" + ref);
                }
            }
        }).start();

        // 稍微睡眠一下,确保前面的线程启动了
        TimeUnit.SECONDS.sleep(3);
        // 暗示JVM进行垃圾回收
        System.gc();
        
    }
}

运行后控制台输出:

虚引用被回收:java.lang.ref.PhantomReference@591f096f

可见,在一个线程中不断去检测引用队列,可以拿到被垃圾回收的虚引用的对象的引用,从而可以进行资源的释放。

一般情况下,都是用自定义的资源释放类来继承虚引用,比如下面的例子:

package references;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author pilaf
 * @description
 * @date 2023-04-05 22:00
 **/
public class PhantomReferenceDemo {

    private static class MyClass{
        private Date birthTime;
        public MyClass(){
            birthTime = new Date();
        }
        // 不能重写finalize方法,否则MyResourceFinalizer就不会被放到引用队列中
        // 原因见:https://stackoverflow.com/questions/48167735/why-the-reference-dont-put-into-reference-queue-when-finalize-method-overrided
//        @Override
//        public void finalize() throws Throwable{
//            System.out.println("finalize invoked..");
//        }
    }

    private static class MyResourceFinalizer extends PhantomReference<MyClass> {
        // 模拟要释放的内存地址
        private String toReleaseAddress = null;
        public MyResourceFinalizer(MyClass referent,ReferenceQueue<MyClass> referenceQueue) {
            super(referent,referenceQueue);
            toReleaseAddress = String.valueOf(referent.hashCode());
        }
        public void releaseResource(){
            System.out.println("释放内存地址:" + toReleaseAddress);
        }
    }


    public static void main(String[] args) throws Exception{
        ReferenceQueue<MyClass> queue = new ReferenceQueue<>();

        List<MyClass> myClassList = new ArrayList<>();
        List<MyResourceFinalizer> myResourceFinalizers = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            MyClass myClass = new MyClass();
            myClassList.add(myClass);
            // 虚引用需要和引用队列一起使用,这样再垃圾回收完虚引用的对象后,它的虚引用会被放到队列中
            myResourceFinalizers.add(new MyResourceFinalizer(myClass, queue));
        }

        // 启动另一个线程来检查是否有虚引用被回收了
        new Thread(()->{
            while (true){
                MyResourceFinalizer ref = (MyResourceFinalizer)queue.poll();
                if (ref!=null){
                    System.out.println("虚引用被回收:" + ref);
                    ref.releaseResource();
                    ref.clear();
                }
            }
        }).start();

        // 稍微睡眠一下,确保前面的线程启动了
        TimeUnit.SECONDS.sleep(2);
        // help gc
        myClassList = null;
        // 暗示JVM进行垃圾回收
        System.gc();


        for (MyResourceFinalizer myResourceFinalizer : myResourceFinalizers) {
            // 输出true,才表示引用进了队列了
            System.out.println(myResourceFinalizer+" isEnqueued:"+myResourceFinalizer.isEnqueued());
        }
    }
}

运行完控制台输出:文章来源地址https://www.toymoban.com/news/detail-406321.html

references.PhantomReferenceDemo$MyResourceFinalizer@2d98a335 isEnqueued:true
references.PhantomReferenceDemo$MyResourceFinalizer@16b98e56 isEnqueued:true
references.PhantomReferenceDemo$MyResourceFinalizer@7ef20235 isEnqueued:true
references.PhantomReferenceDemo$MyResourceFinalizer@27d6c5e0 isEnqueued:true
references.PhantomReferenceDemo$MyResourceFinalizer@4f3f5b24 isEnqueued:true
虚引用被回收:references.PhantomReferenceDemo$MyResourceFinalizer@2d98a335
释放内存地址:1265094477
虚引用被回收:references.PhantomReferenceDemo$MyResourceFinalizer@16b98e56
释放内存地址:2125039532
虚引用被回收:references.PhantomReferenceDemo$MyResourceFinalizer@4f3f5b24
释放内存地址:1554874502
虚引用被回收:references.PhantomReferenceDemo$MyResourceFinalizer@7ef20235
释放内存地址:312714112
虚引用被回收:references.PhantomReferenceDemo$MyResourceFinalizer@27d6c5e0
释放内存地址:692404036

到了这里,关于Java中的四种引用类型及其使用方式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • POST请求中的四种数据类型

    为什么POST请求中会出现这些数据类型呢? 不同类型的数据用对应的编码格式效率比较高 便于数据校验 灵活性比较高 设定一些标准 Content-Type 包含三个指令: media type:声明传输数据的媒体类型( MIME ); charset:声明传输数据采用的是何种字符集; boundary:数据分界符,有

    2024年02月08日
    浏览(44)
  • CSS中的四种定位方式

    在CSS中定位有以下4种: 静态定位 - static 相对定位 - relative 绝对定位 - absolute 固定定位 - fixed 静态定位是css中的默认定位方式,也就是没有定位。在此定位方式中设置:top,bottom,left,right,z-index 这些属性都是无效的。 相对位置前的位置: 相对位置后的位置: 可以看到该

    2024年02月08日
    浏览(81)
  • JavaScript中的四种枚举方式

    字符串和数字具有无数个值,而其他类型如布尔值则是有限的集合。 一周的日子(星期一,星期二,...,星期日),一年的季节(冬季,春季,夏季,秋季)和基本方向(北,东,南,西)都是具有有限值集合的例子。 当一个变量有一个来自有限的预定义常量的值时,使用

    2024年02月03日
    浏览(50)
  • 【C++干货铺】C++中的四种类型转换

    ========================================================================= 个人主页点击直达:小白不是程序员 C++系列专栏:C++干货铺 代码仓库:Gitee ========================================================================= 目录 C语言中的类型转换 为什么C++需要四种类型转化 C++强制类型转换 static_cast reinter

    2024年01月25日
    浏览(38)
  • Service 在 K8s 中的四种类型

    CSDN话题挑战赛第2期 参赛话题:万家争鸣的云计算修罗场 ClusterIP模式 clusterIP 主要在每个 node 节点使用 iptables,将发向 clusterIP 对应端口的数据,转发到 kube-proxy 中。然后 kube-proxy 自己内部实现有负载均衡的方法,并可以查询到这个 service 下对应 pod 的地址和端口,进而把数据

    2024年02月14日
    浏览(35)
  • Java创建数组的四种方式

    1.使用默认值来初始化 语法: 数组元素类型 [] 数组名称 = new 数组元素类型 [数组长度] EG: int [] nums = new int [5]; //创建了一个类型为int,名字为nums ,长度为5的数组 2.先声明一个数组,再给值 语法: 数据元素类型 [] 数组名称; 数组名称 = new 数组元素类型[数组长度]; EG: int [] nums; num

    2024年02月09日
    浏览(51)
  • Java 四种引用类型(强引用、软引用、弱引用、虚引用)

    在 Java 中,基本类型和引用类型是两种不同的数据类型 基本类型直接表示数值、字符或布尔值,直接将值存储在内存,包括 byte、short、int 、long、float 、double、char、boolean 引用类似 C 语言中的指针,在内存中存储的是对象的引用而不是对象本身,通过这个引用可以操作对象

    2024年02月05日
    浏览(47)
  • java对接webservice接口的四种方式

    这两天一直在做外系统对接,对方的接口是webservice的形式,调用起来有些蛋疼,于是在这里记录一下我尝试过的调用WebService的三种方式。 方式一:以HttpURLConnection的方式调用 方式二:使用apache-cxf生成java类调用 下载apache-cxf并配置环境变量(参照JAVA环境变量配置),配置成

    2024年02月09日
    浏览(36)
  • java基础Object转String的四种方式

    java中Object转String有以下几种方法: 1.object.toString()方法 这种方法要注意的是object不能为null,否则会报NullPointException,一般别用这种方法。 2.String.valueOf(object)方法 这种方法不必担心object为null的问题,若为null,会将其转换为”null”字符串,而不是null。这一点要特别注意。”

    2023年04月08日
    浏览(37)
  • Java中的四种权限修饰符

    在Java中,存在四种访问修饰符,它们是public、private、protected和default。它们的访问权限从高到低依次为public protected default private。 1.public:public修饰的类、属性或方法可以被任何其他类访问,包括外部的类、同一个包内的类以及子类。 例如: 在这个例子中,Person类被声明为

    2024年02月12日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包