Java四种引用类型

Wesley13
• 阅读 583

引用与对象

每种编程语言都有自己操作内存中元素的方式,例如在 C 和 C++ 里是通过指针,而在 Java 中则是通过“引用”。
在 Java 中一切都被视为了对象,但是我们操作的标识符实际上是对象的一个引用(reference)。

//创建一个引用,引用可以独立存在,并不一定需要与一个对象关联
String s;

通过将这个叫“引用”的标识符指向某个对象,之后便可以通过这个引用来实现操作对象了。

String str = new String("abc");
System.out.println(str.toString());

在 JDK1.2 之前,Java中的定义很传统:如果 reference 类型的数据中存储的数值代表的是另外一块内存的起始地址,就称为这块内存代表着一个引用。
Java 中的垃圾回收机制在判断是否回收某个对象的时候,都需要依据“引用”这个概念。
在不同垃圾回收算法中,对引用的判断方式有所不同:

  • 引用计数法:为每个对象添加一个引用计数器,每当有一个引用指向它时,计数器就加1,当引用失效时,计数器就减1,当计数器为0时,则认为该对象可以被回收(目前在Java中已经弃用这种方式了)。
  • 可达性分析算法:从一个被称为 GC Roots 的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。

JDK1.2 之前,一个对象只有“已被引用”和"未被引用"两种状态,这将无法描述某些特殊情况下的对象,比如,当内存充足时需要保留,而内存紧张时才需要被抛弃的一类对象。

四种引用类型

所以在 JDK.1.2 之后,Java 对引用的概念进行了扩充,将引用分为了:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4 种,这 4 种引用的强度依次减弱。

一,强引用

Java中默认声明的就是强引用,比如:

Object obj = new Object(); //只要obj还指向Object对象,Object对象就不会被回收
obj = null;  //手动置null

只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了

二,软引用

软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。
在 JDK1.2 之后,用java.lang.ref.SoftReference类来表示软引用。

下面以一个例子来进一步说明强引用和软引用的区别:
在运行下面的Java代码之前,需要先配置参数 -Xms2M -Xmx3M,将 JVM 的初始内存设为2M,最大可用内存为 3M。

首先先来测试一下强引用,在限制了 JVM 内存的前提下,下面的代码运行正常

public class TestOOM {
    
    public static void main(String[] args) {
         testStrongReference();
    }
    private static void testStrongReference() {
        // 当 new byte为 1M 时,程序运行正常
        byte[] buff = new byte[1024 * 1024 * 1];
    }
}

但是如果我们将

byte[] buff = new byte[1024 * 1024 * 1];

替换为创建一个大小为 2M 的字节数组

byte[] buff = new byte[1024 * 1024 * 2];

则内存不够使用,程序直接报错,强引用并不会被回收
Java四种引用类型

接着来看一下软引用会有什么不一样,在下面的示例中连续创建了 10 个大小为 1M 的字节数组,并赋值给了软引用,然后循环遍历将这些对象打印出来。

public class TestOOM {
    private static List<Object> list = new ArrayList<>();
    public static void main(String[] args) {
         testSoftReference();
    }
    private static void testSoftReference() {
        for (int i = 0; i < 10; i++) {
            byte[] buff = new byte[1024 * 1024];

打印结果:
Java四种引用类型

我们发现无论循环创建多少个软引用对象,打印结果总是只有最后一个对象被保留,其他的obj全都被置空回收了。
这里就说明了在内存不足的情况下,软引用将会被自动回收。
值得注意的一点 , 即使有 byte[] buff 引用指向对象, 且 buff 是一个strong reference, 但是 SoftReference sr 指向的对象仍然被回收了,这是因为Java的编译器发现了在之后的代码中, buff 已经没有被使用了, 所以自动进行了优化。
如果我们将上面示例稍微修改一下:

    private static void testSoftReference() {
        byte[] buff = null;

        for (int i = 0; i < 10; i++) {
            buff = new byte[1024 * 1024];
            SoftReference<byte[]> sr = new SoftReference<>(buff);
            list.add(sr);
        }

        System.gc(); //主动通知垃圾回收
        
        for(int i=0; i < list.size(); i++){
            Object obj = ((SoftReference) list.get(i)).get();
            System.out.println(obj);
        }

        System.out.println("buff: " + buff.toString());
    }

则 buff 会因为强引用的存在,而无法被垃圾回收,从而抛出OOM的错误。
Java四种引用类型

如果一个对象惟一剩下的引用是软引用,那么该对象是软可及的(softly reachable)。垃圾收集器并不像其收集弱可及的对象一样尽量地收集软可及的对象,相反,它只在真正 “需要” 内存时才收集软可及的对象。

三,弱引用

弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用。
我们以与软引用同样的方式来测试一下弱引用:

    private static void testWeakReference() {
        for (int i = 0; i < 10; i++) {
            byte[] buff = new byte[1024 * 1024];
            WeakReference<byte[]> sr = new WeakReference<>(buff);
            list.add(sr);
        }
        
        System.gc(); //主动通知垃圾回收
        
        for(int i=0; i < list.size(); i++){
            Object obj = ((WeakReference) list.get(i)).get();
            System.out.println(obj);
        }
    }

打印结果:
Java四种引用类型

可以发现所有被弱引用关联的对象都被垃圾回收了。

四,虚引用

虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用。

public class PhantomReference<T> extends Reference<T> {
    /**
     * Returns this reference object's referent.  Because the referent of a
     * phantom reference is always inaccessible, this method always returns
     * <code>null</code>.
     *
     * @return  <code>null</code>
     */
    public T get() {
        return null;
    }
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

那么传入它的构造方法中的 ReferenceQueue 又是如何使用的呢?

五,引用队列(ReferenceQueue)

引用队列可以与软引用、弱引用以及虚引用一起配合使用,当垃圾回收器准备回收一个对象时,如果发现它还有引用,那么就会在回收对象之前,把这个引用加入到与之关联的引用队列中去。程序可以通过判断引用队列中是否已经加入了引用,来判断被引用的对象是否将要被垃圾回收,这样就可以在对象被回收之前采取一些必要的措施。

与软引用、弱引用不同,虚引用必须和引用队列一起使用。

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
2年前
java 复制Map对象(深拷贝与浅拷贝)
java复制Map对象(深拷贝与浅拷贝)CreationTime2018年6月4日10点00分Author:Marydon1.深拷贝与浅拷贝  浅拷贝:只复制对象的引用,两个引用仍然指向同一个对象
待兔 待兔
3年前
Java多态实现原理
Java多态概述多态是面向对象编程语言的重要特性,它允许基类的指针或引用指向派生类的对象,而在具体访问时实现方法的动态绑定。Java对于方法调用动态绑定的实现主要依赖于方法表,但通过类引用调用(invokevirtual)和接口引用调用(invokeinterface)的实现则有所不同。类引用调用的大致过程为:Java编译器将Java源代码编译成c
xiguaapp xiguaapp
3年前
垃圾回收机制
GC标记算法对象被判定为垃圾的标准:没有被其他对象引用引用计数算法:判断对象的引用数量:通过判断对象的引用数量来决定对象是否可以被回收每个对象实例都有一个引用计数器,被引用则1,完成引用则1任何引用计数为0的对象实例可以被当做垃圾收集优点:执行效率高,程序执行受影响较小。
Stella981 Stella981
2年前
JavaScript的深拷贝和浅拷贝
一、数据类型数据分为基本数据类型(String,Number,Boolean,Null,Undefined,Symbol)和对象数据类型。、1.基本数据类型的特点:直接存储在栈(stack)中的数据2.引用数据类型的特点:存储的是该对象在栈中引用,真实的数据放在堆内存里。引用数据类型在栈中存储了指针,该指针指向堆中该实
Wesley13 Wesley13
2年前
Java对象的引用类型
!(https://oscimg.oschina.net/oscnet/24d3ddce8c92eb32f8e3a68063234324da7.jpg)     Java对象的引用类型有强引用,软引用,弱引用,虚引用和FinalReference,提供这几种引用类型的主要目的:1.程序员可以通过不同的引用方式决定某些对象的生命周期;2.
Wesley13 Wesley13
2年前
Java基础学习总结(8)——super关键字
一、super关键字  在JAVA类中使用super来引用父类的成分,用this来引用当前对象,如果一个类从另外一个类继承,我们new这个子类的实例对象的时候,这个子类对象里面会有一个父类对象。怎么去引用里面的父类对象呢?使用super来引用,this指的是当前对象的引用,super是当前对象里面的父对象的引用。
Stella981 Stella981
2年前
JVM调优总结一
数据类型   Java虚拟机中,数据类型可以分为两类:基本类型和引用类型。基本类型的变量保存原始值,即:他代表的值就是数值本身;而引用类型的变量保存引用值。“引用值”代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。基本类型包括:byte,short,int,long,cha
Easter79 Easter79
2年前
Spring表达式语言:SpEL
1、Spring表达式语言(简称:SpEL):是一个支持运行时查询和操作对象图的强大的表达式语言。2、语法类似于EL:SpEL使用{...}作为定界符,所有在大括号内的字符都被认为是SpEL。3、SpEL为bean的属性进行动态赋值提供了便利。4、通过SpEL可以实现:\通过bean的id对bean进行引用\调用方法以及引用对象
Wesley13 Wesley13
2年前
Java虚拟机篇(面试)
!面试重点:Java虚拟机篇(http://p3.pstatp.com/large/5e84000330ca26c3c6c6)一、Java引用的四种状态:强引用:  用的最广。我们平时写代码时,new一个Object存放在堆内存,然后用一个引用指向它,这就是强引用。  如果一个对象具有强引用,那垃圾回
Stella981 Stella981
2年前
JVM调优总结(一)基本概念
数据类型Java虚拟机中,数据类型可以分为两类:基本类型和引用类型。    基本类型:保存原始值,即:他代表的值就是数值本身;    引用类型:保存引用值。“引用值”代表了某个对象的引用,而不是对象本身,对象本身存放在这个引