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 | +-------+ |
discoveredActive状态下指向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] |
