java中的引用
jvm中的Reference Handler
线程
经常看jstack
的输出就会发现一个常见的线程 – Reference Handler
, 堆栈如下:
1 | "Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f873013e000 nid=0x18d7 in Object.wait() [0x00007f873443b000] |
看线程栈, 执行到Reference
下的tryHandlePending
方法就成了WAIT
状态, 带着好奇心去翻翻源码.
何时启动?
1 | // Reference类中的静态代码块 |
Reference
在被加载的时候就会触发static
里的代码执行, 就会创建Reference Handler
线程并启动.
至于SharedSecrets
, 这个类类似一个Holder, 保存了一些对象的引用, 并提供了一些get/set方法.
1 | public class SharedSecrets { |
这个线程做了什么?
1 | // java.lang.ref.Reference.ReferenceHandler |
这个线程其实只是在死循环中调用了Reference
的tryHandlePending
来清理无效的Reference
.
Reference
Java中有四种引用, StrongReference
, SoftReference
, WeakReference
, PhantomReference
, 除了StrongReference
其他的三种都有对应的类:
引用的状态
Active
: Newly-created instances are Active.Pending
: An element of the pending-Reference list, waiting to be enqueued by the Reference-handler thread. Unregistered instances are never in this state.(没有注册ReferenceQueue的不会有这个状态)Enqueued
: An element of the queue with which the instance was registered when it was created. When an instance is removed from its ReferenceQueue, it is made Inactive. Unregistered instances are never in this state.(没有注册ReferenceQueue的不会有这个状态)Inactive
: Nothing more to do. Once an instance becomes Inactive its state will never change again.(终态)
引用链表
Reference
中有五个关键的变量:
1 | private T referent; /* Treated specially by GC */ |
referent
代表当前持有的引用.
queue
是初始化的时候传入的, 可以为null, 如果传入了
ReferenceQueue
, 会有入队列和出队列的操作next
代表
ReferenceQueue
中的下一个,Active
状态下是null,Pending
和Inactive
状态下指向自己,1
2
3
4
5
6
7
8
9+-------+
| |
v |
|
+-----+ |
| | |
| 1 +----+
| |
+-----+Enqueued
状态下指向queue
中的下一个元素
1 | +-------+ |
discovered
Active
状态下指向discovered reference list
中的下一个元素,Pending
状态下指向pending list
中的下一个元素,
其他状态下是null, 相关的链表是JVM维护的pending
pending
指向的是pending list
链表的head
tryHandlePending
处理引用的代码:
1 | /** |
ReferenceQueue
ReferenceQueue
用一个链表来维护队列里的Reference
入队列相关的操作:
1 | boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */ |
出队列相关的操作:
1 | "unchecked") ( |
openjdk
上面两个变量对应在VM中的调用,可以参考openjdk中的hotspot源码,在hotspot/src/share/vm/memory/referenceProcessor.cpp 的ReferenceProcessor::discover_reference 方法。(根据此方法的注释由了解到虚拟机在对Reference的处理有ReferenceBasedDiscovery和RefeferentBasedDiscovery两种策略)
1 | void ReferenceProcessor::enqueue_discovered_reflist(DiscoveredList& refs_list, |
GC日志
在jvm的启动参数中加入下面的flag, 可以打开处理引用用掉的时间:
1 | -XX:+PrintGCDetails -XX:+PrintReferenceGC |
测试代码:
1 |
|
日志输出:
1 | [GC (System.gc()) [SoftReference, 0 refs, 0.0000616 secs][WeakReference, 34 refs, 0.0000273 secs][FinalReference, 654 refs, 0.0003785 secs][PhantomReference, 0 refs, 0 refs, 0.0000060 secs][JNI Weak Reference, 0.0000023 secs][PSYoungGen: 10708K->1566K(56320K)] 10708K->1574K(184832K), 0.0083391 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] |