一文读懂java中的Reference和引用类型

简介

java中有值类型也有引用类型,引用类型一般是针对于java中对象来说的,今天介绍一下java中的引用类型。java为引用类型专门定义了一个类叫做Reference。Reference是跟java垃圾回收机制息息相关的类,通过探讨Reference的实现可以更加深入的理解java的垃圾回收是怎么工作的。

本文先从java中的四种引用类型开始,一步一步揭开Reference的面纱。

java中的四种引用类型分别是:强引用,软引用,弱引用和虚引用。

强引用Strong Reference

java中的引用默认就是强引用,任何一个对象的赋值操作就产生了对这个对象的强引用。

我们看一个例子:

public class ReferenceUsage {

    @Test
    public void StrongReference(){
        Object obj = new Object();   // =直接赋予new出的新对象一个强引用
    }
}

上面我们new了一个Object对象,并将其赋值给obj,这个obj就是new Object()的强引用。

强引用的特性是只要有强引用存在,被引用的对象就不会被垃圾回收。

软引用Soft Reference

软引用在java中有个专门的SoftReference类型,软引用的意思是只有在内存不足的情况下,被引用的对象才会被回收。

先看下SoftReference的定义:

public class SoftReference<T> extends Reference<T>

SoftReference继承自Reference。它有两种构造函数:

    public SoftReference(T referent) 

    public SoftReference(T referent, ReferenceQueue<? super T> q)

第一个参数很好理解,就是软引用的对象,第二个参数叫做ReferenceQueue,是用来存储封装的待回收Reference对象的,ReferenceQueue中的对象是由Reference类中的ReferenceHandler内部类进行处理的。

我们举个SoftReference的例子:

/**
 * @Author Duanzh
 * @Description 软引用测试:内存不足即回收 -Xms10m -Xmx10m -XX:+PrintGC
 * @Date 2022/4/13 14:42
 * @Version 1.0
 */
public class SoftReferenceTest {
    public static class User {
        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }

        public int id;
        public String name;

        @Override
        public String toString() {
            return "[id=" + id + ", name=" + name + "] ";
        }
    }

    public static void main(String[] args) {
        // 创建对象,建立软引用
        // SoftReference<User> userSoftRef = 
        //        new SoftReference<User>(new User(1, "songhk"));
        // 上面的一行代码,等价于如下的三行代码
        User u1 = new User(1, "cakin");
        SoftReference<User> userSoftRef = new SoftReference<User>(u1);
        u1 = null;    //取消强引用
        // 从软引用中重新获得强引用对象
        System.out.println(userSoftRef.get());
        // 主动触发gc,此时内存应该足够
        System.gc();
        System.out.println("内存足够:After GC:");
        // 垃圾回收之后获得软引用中的对象
        System.out.println(userSoftRef.get()); // 由于堆空间内存足够,所以不会回收软引用的可达对象。
        System.out.println("内存紧张或不足:After GC:");
        try {
            // 让系统认为内存资源不够 a
             byte[] b = new byte[1024 * 1024 * 7];
            // 让系统认为内存资源紧张 b
            //byte[] b = new byte[1024 * 7000 - 630 * 1024];
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            // 再次从软引用中获取数据
            System.out.println(userSoftRef.get()); // 在报 OOM 之前,垃圾回收器会回收软引用的可达对象。
        }
    }

}

输出结果:

a:(OOM,finally中获取到的值为null)

[id=1, name=cakin] 
[GC (System.gc()) [PSYoungGen: 1687K->496K(2560K)] 2092K->1061K(9728K), 0.0013469 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 496K->0K(2560K)] [ParOldGen: 565K->876K(7168K)] 1061K->876K(9728K), [Metaspace: 3159K->3159K(1056768K)], 0.0058966 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
内存足够:After GC:
[id=1, name=cakin] 
内存紧张或不足:After GC:
[GC (Allocation Failure) [PSYoungGen: 80K->96K(2560K)] 957K->972K(9728K), 0.0004120 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 96K->32K(2560K)] 972K->908K(9728K), 0.0004279 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 32K->0K(2560K)] [ParOldGen: 876K->833K(7168K)] 908K->833K(9728K), [Metaspace: 3160K->3160K(1056768K)], 0.0067242 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 833K->833K(9728K), 0.0004942 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 833K->817K(7168K)] 833K->817K(9728K), [Metaspace: 3160K->3160K(1056768K)], 0.0078196 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
null
Heap
 PSYoungGen      total 2560K, used 101K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 4% used [0x00000000ffd00000,0x00000000ffd196e8,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 7168K, used 817K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 11% used [0x00000000ff600000,0x00000000ff6cc6a8,0x00000000ffd00000)
 Metaspace       used 3191K, capacity 4556K, committed 4864K, reserved 1056768K
  class space    used 341K, capacity 392K, committed 512K, reserved 1048576K
java.lang.OutOfMemoryError: Java heap space
	at com.tuling._03_list.SoftReferenceTest.main(SoftReferenceTest.java:44)

 b:(老年代已99%,内存紧张,但没有OOM,finally中获取到的值为null)

[id=1, name=cakin] 
[GC (System.gc()) [PSYoungGen: 1679K->488K(2560K)] 2100K->1121K(9728K), 0.0012731 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 488K->0K(2560K)] [ParOldGen: 633K->877K(7168K)] 1121K->877K(9728K), [Metaspace: 3159K->3159K(1056768K)], 0.0078880 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
内存足够:After GC:
[id=1, name=cakin] 
内存紧张或不足:After GC:
[GC (Allocation Failure) [PSYoungGen: 80K->64K(2560K)] 958K->941K(9728K), 0.0005351 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 64K->96K(2560K)] 941K->973K(9728K), 0.0005417 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 96K->0K(2560K)] [ParOldGen: 877K->813K(7168K)] 973K->813K(9728K), [Metaspace: 3160K->3160K(1056768K)], 0.0081207 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 813K->813K(9728K), 0.0004101 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 813K->797K(7168K)] 813K->797K(9728K), [Metaspace: 3160K->3160K(1056768K)], 0.0068592 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
null
Heap
 PSYoungGen      total 2560K, used 20K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 1% used [0x00000000ffd00000,0x00000000ffd053c0,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 7168K, used 7167K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 99% used [0x00000000ff600000,0x00000000ffcffeb8,0x00000000ffd00000)
 Metaspace       used 3167K, capacity 4556K, committed 4864K, reserved 1056768K
  class space    used 338K, capacity 392K, committed 512K, reserved 1048576K

可以看到在内存不足的情况下,SoftReference引用的对象会被回收。

弱引用weak Reference

weakReference和softReference很类似,不同的是weekReference引用的对象只要垃圾回收执行,就会被回收,而不管是否内存不足。

同样的WeakReference也有两个构造函数:

     public WeakReference(T referent);

     public WeakReference(T referent, ReferenceQueue<? super T> q);

含义和SoftReference一致,这里就不再重复表述了。

我们看下弱引用的例子:

    @Test
    public void weakReference() throws InterruptedException {
        Object obj = new Object();
        WeakReference<Object> weak = new WeakReference<>(obj);
        obj = null;
        log.info("{}",weak.get());
        System.gc();
        log.info("{}",weak.get());
    }

输出结果:

22:58:02.019 [main] INFO com.flydean.WeakReferenceUsage - java.lang.Object@71bc1ae4
22:58:02.047 [main] INFO com.flydean.WeakReferenceUsage - null

结论:只要有gc,就会回收弱引用。

虚引用PhantomReference

PhantomReference的作用是跟踪垃圾回收器收集对象的活动,在GC的过程中,如果发现有PhantomReference,GC则会将引用放到ReferenceQueue中,由程序员自己处理,当程序员调用ReferenceQueue.poll()方法,将引用从ReferenceQueue移除之后,Reference对象会变成Inactive状态,意味着被引用的对象可以被回收了。

和SoftReference和WeakReference不同的是,PhantomReference只有一个构造函数,必须传入ReferenceQueue:

    public PhantomReference(T referent, ReferenceQueue<? super T> q)

看一个PhantomReference的例子:

@Slf4j
public class PhantomReferenceUsage {

    @Test
    public void usePhantomReference(){
        ReferenceQueue<Object> rq = new ReferenceQueue<>();
        Object obj = new Object();
        PhantomReference<Object> phantomReference = new PhantomReference<>(obj,rq);
        obj = null;
        log.info("{}",phantomReference.get());
        System.gc();
        Reference<Object> r = (Reference<Object>)rq.poll();
        log.info("{}",r);
    }
}

运行结果:

07:06:46.336 [main] INFO com.flydean.PhantomReferenceUsage - null
07:06:46.353 [main] INFO com.flydean.PhantomReferenceUsage - java.lang.ref.PhantomReference@136432db

我们看到get的值是null,而GC过后,poll是有值的。

因为PhantomReference引用的是需要被垃圾回收的对象,所以在类的定义中,get一直都是返回null:

    public T get() {
        return null;
    }

Reference和ReferenceQueue

讲完上面的四种引用,接下来我们谈一下他们的父类Reference和ReferenceQueue的作用。

Reference是一个抽象类,每个Reference都有一个指向的对象,在Reference中有5个非常重要的属性:referent,next,discovered,pending,queue。

private T referent;         /* Treated specially by GC */
volatile ReferenceQueue<? super T> queue;
volatile Reference next;
transient private Reference<T> discovered;  /* used by VM */
private static Reference<Object> pending = null;

每个Reference都可以看成是一个节点,多个Reference通过next,discovered和pending这三个属性进行关联。

先用一张图来对Reference有个整体的概念:

referent就是Reference实际引用的对象。

通过next属性,可以构建ReferenceQueue。

通过discovered属性,可以构建Discovered List。

通过pending属性,可以构建Pending List。

四大状态

在讲这三个Queue/List之前,我们先讲一下Reference的四个状态:

 

从上面的图中,我们可以看到一个Reference可以有四个状态。

因为Reference有两个构造函数,一个带ReferenceQueue,一个不带。


    Reference(T referent) {
        this(referent, null);
    }

    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }

对于带ReferenceQueue的Reference,GC会把要回收对象的Reference放到ReferenceQueue中,后续该Reference需要程序员自己处理(调用poll方法)。

不带ReferenceQueue的Reference,由GC自己处理,待回收的对象其Reference状态会变成Inactive。

创建好了Reference,就进入active状态。

active状态下,如果引用对象的可到达状态发送变化就会转变成Inactive或Pending状态。

Inactive状态很好理解,到达Inactive状态的Reference状态不能被改变,会等待GC回收。

Pending状态代表等待入Queue,Reference内部有个ReferenceHandler,会调用enqueue方法,将Pending对象入到Queue中。

入Queue的对象,其状态就变成了Enqueued。

Enqueued状态的对象,如果调用poll方法从ReferenceQueue拿出,则该Reference的状态就变成了Inactive,等待GC的回收。

这就是Reference的一个完整的生命周期。

三个Queue/List

有了上面四个状态的概念,我们接下来讲三个Queue/List:ReferenceQueue,discovered List和pending List。

ReferenceQueue在讲状态的时候已经讲过了,它本质是由Reference中的next连接而成的。用来存储GC待回收的对象。

pending List就是待入ReferenceQueue的list。

discovered List这个有点特别,在Pending状态时候,discovered List就等于pending List。

在Active状态的时候,discovered List实际上维持的是一个引用链。通过这个引用链,我们可以获得引用的链式结构,当某个Reference状态不再是Active状态时,需要将这个Reference从discovered List中删除。

WeakHashMap

最后讲一下WeakHashMap,WeakHashMap跟WeakReference有点类似,在WeakHashMap如果key不再被使用,被赋值为null的时候,该key对应的Entry会自动从WeakHashMap中删除。

我们举个例子:

    @Test
    public void useWeakHashMap(){
        WeakHashMap<Object, Object> map = new WeakHashMap<>();
        Object key1= new Object();
        Object value1= new Object();
        Object key2= new Object();
        Object value2= new Object();

        map.put(key1, value1);
        map.put(key2, value2);
        log.info("{}",map);

        key1 = null;
        System.gc();
        log.info("{}",map);

    }

输出结果:

[main] INFO com.flydean.WeakHashMapUsage - {java.lang.Object@14899482=java.lang.Object@2437c6dc, java.lang.Object@11028347=java.lang.Object@1f89ab83}
[main] INFO com.flydean.WeakHashMapUsage - {java.lang.Object@14899482=java.lang.Object@2437c6dc}

可以看到gc过后,WeakHashMap只有一个Entry了。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于Java泛型类型的参数传入,我们需要先了解一下Java泛型的基本概念。 Java的泛型是一种参数化类型的概念,即在定义类、接口或方法时,使用一个或多个类型参数来表示其的某些类型,这些类型参数在使用时再被具体化。通过使用泛型,可以使代码更加通用、安全和可读性更强。 Java的泛型类型参数可以用于类、接口和方法的定义。在使用时,需要将具体的类型参数传递给它们,以指定其的泛型类型。 下面以一个简单的例子来说明Java参数传入泛型类型的用法。 ``` public class Box<T> { private T data; public Box(T data) { this.data = data; } public T getData() { return data; } public void setData(T data) { this.data = data; } } ``` 在这个例子,我们定义了一个泛型类Box,其的类型参数T可以在类的定义被指定。在Box类的构造函数和getData、setData方法,我们使用了泛型类型T来表示其的某些类型。 现在我们可以创建一个Box对象,并将一个具体的类型参数传递给它,以指定其的泛型类型。例如: ``` Box<Integer> box = new Box<Integer>(new Integer(10)); ``` 在这个例子,我们创建了一个Box对象,并将Integer类型作为泛型类型参数传递给它。这样一来,我们就可以在Box对象存储和获取Integer类型的数据了。 同样地,我们也可以创建其他类型的Box对象,例如: ``` Box<String> box = new Box<String>("Hello World!"); Box<Double> box = new Box<Double>(new Double(3.14)); ``` 通过这种方式,我们可以方便地定义、使用和重用泛型类型,从而使代码更加通用和灵活。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值