码迷,mamicode.com
首页 > 编程语言 > 详细

Java引用类型

时间:2020-08-15 23:52:30      阅读:53      评论:0      收藏:0      [点我收藏+]

标签:hot   method   href   句柄   直接   handle   weakref   作者   rem   

Java中一共有4种引用类型(其实还有一些其他的引用类型比如FinalReference):强引用、软引用、弱引用、虚引用。其中强引用就是如下的情况:

 Object a=new Object(); 

obj持有的Object对象的引用就是强引用,在Java中并没有对应的Reference类。

本篇文章主要是分析软引用、弱引用、虚引用的实现,这三种引用类型都是继承于Reference这个类,主要逻辑也在Reference中。

Reference是java中的引用类,它用来给普通对象进行包装,当JVM在GC时,按照引用类型的不同,在回收时执行不同的逻辑。先来看下这个类的继承体系:

技术图片 

由图可知,Java存在以下几种引用: 

Java中的引用有: 

  • 强引用(StrongReference):强引用就是我们平时创建对象,创建数组时的引用。强引用在任何时候都不会被GC回收掉;
  • 软引用(SoftReference):软引用是在系统发生OOM之前才被JVM回收掉。软引用常被用来对于内存敏感的缓存;
  • 弱引用(WeakReference):一旦JVM执行GC,弱引用就会被回收掉;
  • 虚引用(PhantomReference):虚引用主要作为其指向referent被回收时的一种通知机制;
  • FinalReference:用于收尾机制(finalization) 。

引用实例的几个状态 

  • Active:当处于Active状态,GC会特殊处理引用实例,一旦GC检测到其可达性发生变化,GC就会更改其状态。此时分两种情况,如果该引用实例创建时有注册引用队列,则会进入Pending状态,否则会进入Inactive状态。新创建的引用实例为Active。

  • Pending:当前为pending列表中的一个元素,等待被ReferenceHandler线程消费并加入其注册的引用队列。如果该引用实例未注册引用队列,则永远不会处于这个状态。

  • Enqueued:该引用实例创建时有注册引用队列并且当前处于入队列状态,属于该引用队列中的一个元素。当该引用实例从其注册引用队列中移除后其状态变为Inactive。如果该引用实例未注册引用队列,则永远不会处于这个状态。

  • Inactive:当处于Inactive状态,无需任何处理,一旦变成Inactive状态则其状态永远不会再发生改变。整体迁移流程图如下:

整体迁移流程图如下:

技术图片 

如上的状态是为了更好的理解而虚拟出来的状态,并没有一个字段来描述状态,而是通过queue和next字段来标记的。

  • Active:当实例注册了引用队列,则queue = ReferenceQueue;当实例没有注册引用队列,那么queue = ReferenceQueue.NULL。next = null;
  • Pending:处在这个状态下的实例肯定注册了引用队列,queue = ReferenceQueue。next = this;
  • Enqueued:处在这个状态下的实例肯定注册了引用队列,queue = ReferenceQueue.ENQUEUED,next指向下一个在此队列中的元素,或者如果队列中只有当前对象时为当前对象this;
  • Inactive:queue = ReferenceQueue.NULL;next = this。

1、Reference

我们先看下 Reference类及重要属性的定义如下:

public abstract class Reference<T> {	
    //引用的对象	
    private T referent;   
     	
    //回收队列,由使用者在Reference的构造函数中指定	
    volatile ReferenceQueue<? super T> queue;
	
     //当该引用被加入到queue中的时候,该字段被设置为queue中的下一个元素,以形成链表结构	
    volatile Reference  next;	

    //在GC时,HotSpot底层会维护一个叫DiscoveredList的链表,存放的是Reference对象,discovered字段指向的就是链表中的下一个元素,由HotSpot设置	
    transient private Reference<T>  discovered; 
 	
    //进行线程同步的锁对象	
    static private class Lock { }	
    private static Lock  lock = new Lock();
	
    //等待加入queue的Reference对象,在GC时由JVM设置,会有一个java层的线程(ReferenceHandler)源源不断的从pending中提取元素加入到queue	
    private static Reference<Object> pending = null;	
}

注意Reference指的是引用对象,而Referent指的是所指对象。

一个Reference对象的生命周期如下:  

技术图片 

HotSpot在GC时将需要被回收的Reference对象加入到DiscoveredList中,然后将DiscoveredList的元素移动到PendingList中。PendingList的队首元素由Reference类中的pending属性持有。 

2、ReferenceHandler

ReferenceHandler的代码实现如下:

private static class ReferenceHandler extends Thread {	
         ...	
        public void run() {	
            while (true) {	
                 tryHandlePending(true);	
            }	
        }	
} 	

static boolean tryHandlePending(boolean waitForNotify) {	
        Reference<Object> r;	
        Cleaner c;	
        try {	
            synchronized (lock) {	
                if (pending != null) {	
                     r = pending;	
                     // 如果是Cleaner对象,则记录下来,下面做特殊处理	
                     c = r instanceof Cleaner ? (Cleaner) r : null;	
                     // 指向PendingList的下一个对象	
                     pending = r.discovered;	
                     r.discovered = null;	
                } else {	
                     // 如果pending为null就先等待,当有对象加入到PendingList中时,jvm会执行notify	
                     if (waitForNotify) {	
                         lock.wait();	
                     }	
                     // retry if waited	
                     return waitForNotify;	
                }	
            }	
        } 	
        ...	
        // 如果时Cleaner对象,则调用clean方法进行资源回收	
        if (c != null) {	
             c.clean();	
             return true;	
        }	
        // 将Reference加入到ReferenceQueue,开发者可以通过从ReferenceQueue中poll元素感知到对象被回收的事件。	
        ReferenceQueue<? super Object> q = r.queue;	
        if (q != ReferenceQueue.NULL) 
             q.enqueue(r);	
        return true;	
 }

源源不断的从PendingList中获取元素,然后加入到ReferenceQueue中,开发者可以通过调用ReferenceQueue的poll()方法来感知对象被回收的事件。

另外需要注意的是,对于Cleaner类型(继承自虚引用)的对象会有额外的处理:在其指向的对象被回收时,会调用clean()方法,该方法主要是用来做对应的资源回收,在堆外内存DirectByteBuffer中就是用Cleaner进行堆外内存的回收,这也是虚引用在java中的典型应用,后面会详细介绍。

3、ReferenceQueue

ReferenceQueue是引用队列,垃圾收集器在检测到适当的可达性更改后将已注册的引用对象追加到该队列。

public class ReferenceQueue<T> {

    public ReferenceQueue() { }
    
    // 内部类Null类继承自ReferenceQueue,覆盖了enqueue方法返回false
    private static class Null extends ReferenceQueue<Object> {
        boolean enqueue(Reference<?> r) {
            return false;
        }
    }
    
    // ReferenceQueue.NULL和ReferenceQueue.ENQUEUED都是内部类Null的新实例
    static final ReferenceQueue<Object> NULL = new Null();
    static final ReferenceQueue<Object> ENQUEUED = new Null();
    
    // 静态内部类,作为锁对象
    private static class Lock { };
    private final Lock lock = new Lock();
// 引用链表的头节点 private volatile Reference<? extends T> head; // 引用队列长度,入队则增加1,出队则减少1 private long queueLength = 0; // 入队操作,只会被Reference实例调用 boolean enqueue(Reference<? extends T> r) { // 加锁 synchronized (lock) { // Check that since getting the lock this reference hasn‘t already been // enqueued (and even then removed) // 如果引用实例持有的队列为ReferenceQueue.NULL或者ReferenceQueue.ENQUEUED,则入队失败返回false ReferenceQueue<?> queue = r.queue; if ((queue == NULL) || (queue == ENQUEUED)) { return false; } assert queue == this; // Self-loop end, so if a FinalReference it remains inactive. // 如果链表没有元素,则此引用实例直接作为头节点,否则把前一个引用实例作为下一个节点 r.next = (head == null) ? r : head; // 当前实例更新为头节点,也就是每一个新入队的引用实例都是作为头节点,已有的引用实例会作为后继节点 head = r; // 队列长度增加1 queueLength++; // Update r.queue *after* adding to list, to avoid race // with concurrent enqueued checks and fast-path poll(). // Volatiles ensure ordering. // 当前引用实例已经入队,那么它本身持有的引用队列实例置为ReferenceQueue.ENQUEUED r.queue = ENQUEUED; // 特殊处理FinalReference,VM进行计数 if (r instanceof FinalReference) { VM.addFinalRefCount(1); } // 唤醒所有等待的线程 lock.notifyAll(); return true; } } // 引用队列的poll操作,此方法必须在加锁情况下调用 private Reference<? extends T> reallyPoll() { Reference<? extends T> r = head; if (r != null) { r.queue = NULL; // Update r.queue *before* removing from list, to avoid // race with concurrent enqueued checks and fast-path // poll(). Volatiles ensure ordering. @SuppressWarnings("unchecked") Reference<? extends T> rn = r.next; // Handle self-looped next as end of list designator. // 更新next节点为头节点,如果next节点为自身,那么队列中只有当前这个对象一个元素 head = (rn == r) ? null : rn; // Self-loop next rather than setting to null, so if a // FinalReference it remains inactive. // 当前头节点变更为环状队列,考虑到FinalReference尚为inactive和避免重复出队的问题 r.next = r; // 队列长度减少1 queueLength--; // 特殊处理FinalReference,VM进行计数 if (r instanceof FinalReference) { VM.addFinalRefCount(-1); } return r; } return null; } // 队列的公有poll操作,主要是加锁后调用reallyPoll public Reference<? extends T> poll() { if (head == null) return null; synchronized (lock) { return reallyPoll(); } } // ... }

从源码上看,实际上ReferenceQueue只是名义上的引用队列,它只保存了Reference链表的头(head)节点,并且提供了出队、入队等操作,而Reference实际上本身提供单向链表的功能,也就是Reference通过属性next构建单向链表,而链表的操作通过ReferenceQueue这个类来完成。

相关文章的链接如下:

1、在Ubuntu 16.04上编译OpenJDK8的源代码 

2、调试HotSpot源代码

3、HotSpot项目结构 

4、HotSpot的启动过程 

5、HotSpot二分模型(1)

6、HotSpot的类模型(2)  

7、HotSpot的类模型(3) 

8、HotSpot的类模型(4)

9、HotSpot的对象模型(5)  

10、HotSpot的对象模型(6) 

11、操作句柄Handle(7)

12、句柄Handle的释放(8)

13、类加载器 

14、类的双亲委派机制 

15、核心类的预装载

16、Java主类的装载  

17、触发类的装载  

18、类文件介绍 

19、文件流 

20、解析Class文件 

21、常量池解析(1) 

22、常量池解析(2)

23、字段解析(1)

24、字段解析之伪共享(2) 

25、字段解析(3)  

26、字段解析之OopMapBlock(4)

27、方法解析之Method与ConstMethod介绍  

28、方法解析

29、klassVtable与klassItable类的介绍  

30、计算vtable的大小 

31、计算itable的大小 

32、解析Class文件之创建InstanceKlass对象 

33、字段解析之字段注入 

34、类的连接  

35、类的连接之验证 

36、类的连接之重写(1) 

37、类的连接之重写(2)

38、方法的连接  

39、初始化vtable 

40、初始化itable  

41、类的初始化 

作者持续维护的个人博客  classloading.com

关注公众号,有HotSpot源码剖析系列文章!

技术图片  

 

 

  

 

Java引用类型

标签:hot   method   href   句柄   直接   handle   weakref   作者   rem   

原文地址:https://www.cnblogs.com/mazhimazhi/p/13502748.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!