简述
无论是通过引用计数算法
判断对象的引用数量,还是通过可达性分析算法
判断对象是否引用链可达,判定对象是否存活都和“引用”离不开关系。
-
在JDK 1.2版之前,Java里面的引用是很传统的定义:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称该reference数据是代表某块内存、某个对象的引用。
这种定义并没有什么不对,只是现在看来有些过于狭隘了,一个对象在这种定义下只有“
被引用
”或者“未被引用
”两种状态,对于描述一些“食之无味,弃之可惜”的对象就显得无能为力。譬如我们希望能描述一类对象:当内存空间还足够时,能保留在内存之中,如果内存空间在进行垃圾收集后仍然非常紧张,那就可以抛弃这些对象——很多系统的缓存功能都符合这样的应用场景。 -
在JDK 1.2版之后,Java对引用的概念进行了扩充,将引用分为
强引用(Strongly Re-ference)
、软引用(Soft Reference)
、弱引用(Weak Reference)
和虚引用(PhantomReference)
4种,这4种引用强度依次逐渐减弱。-
强引用
:普通的的引用类型,new一个对象默认得到的引用就是强引用,只要对象存在强引用,就不会被GC。 -
软引用
:相对较弱的引用,垃圾回收器会在内存不足时回收弱引用指向的对象。JVM会在抛出OOME前清理所有弱引用指向的对象,如果清理完还是内存不足, 才会抛出OOME。所以软引用一般用于实现内存敏感缓存。 -
弱引用
:更弱的引用类型,垃圾回收器在GC时会回收此对象,也可以用于实现缓存,比如JDK提供的WeakHashMap。 -
虚引用
:一种特殊的引用类型,不能通过虚引用获取到关联对象,只是用于获取对象被回收的通知。
-
假死亡的情况
即使在可达性分析算法中判定为不可达的对象,也不是“非死不可”
的,这时候它们暂时还处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:
- 如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被
第一次标记
。 - 随后进行一次筛选,筛选的条件是此对象是否有必要执行
finalize()
方法。 - 假如对象没有覆盖
finalize()
方法,或者finalize()
方法已经被虚拟机调用过,那么虚拟机将这两种情况都视为“没有必要执行”。只会执行一次
如果这个对象被判定为确有必要执行finalize()
方法,那么该对象将会被放置在一个名为F-Queue
的队列之中,并在稍后由一条由虚拟机自动建立的、低调度优先级的Finalizer线程去执行它们的finalize()方法。
这里所说的“执行”是指虚拟机会触发这个方法
开始运行
,但并不承诺一定会等待它运行结束。这样做的原因是,如果某个对象的finalize()
方法执行缓慢,或者更极端地发生了死循环
,将很可能导致F-Queue队列中的其他对象永久处于等待,甚至导致整个内存回收子系统的崩溃。所以针对这种情况JVM不会等待。
finalize()
方法是对象逃脱死亡命运的最后一次机会
,稍后收集器将对F-Queue
中的对象进行第二次小规模的标记,如果对象要在finalize()
中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移出“即将回收”的集合;如果对象这时候还没有逃脱,那基本上它就真的要被回收了。
从下面代码中我们可以看到一个对象的finalize()
被执行,但是它仍然可以存活。
public class FinalizeEscapeGC{
public static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive(){
System.out.println("yes, i am still alive");
}
@Override
protected void finalize() throws Throwable{
super.finalize();
System.out.println("finalize method executed! ");
FinalizeEscapeGC.SAVE_HOOK = this;
}
public static void main(String[] args) throws Throwable{
SAVE_HOOK = new FinalizeEscapeGC();
//<1> 对象第一次拯救自己 成功
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (SAVE_HOOK != null)
SAVE_HOOK.isAlive();
else {
System.out.println("no, i am dea :(");
}
//<2> 对象第二次拯救自己 失败
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (SAVE_HOOK != null)
SAVE_HOOK.isAlive();
else {
System.out.println("no, i am dea :(");
}
}
}
输出
finalize method executed!
yes, i am still alive
no, i am dea :(
可以看到,SAVE_HOOK对象的finalize()
方法确实被垃圾收集器触发过,并且在被收集前成功逃脱了。
另外一个值得注意的地方就是,代码中有两段完全一样的代码片段,
- 执行结果却是一次逃脱成功,一次失败了。这是因为任何一个对象的
finalize()
方法都只会被系统自动调用一次 - 如果对象面临下一次回收,它的
finalize()
方法不会被再次执行,因此第二段代码的自救行动失败了。
这也验证了在上面分析的假死亡
的情况,finalize()
只会被调用一次。
不建议使用
finalize()
方法:运行代价高昂,不确定性大,无法保证各个对象的调用顺序,如今已被官方明确声明为不推荐使用的语法。
有了上面的关于 引用
和 finalize
方法的一些理论,下面让我们来看一下 Java 中关于引用和 finalize方法的具体实现以及逻辑。
Java有5种类型的可达性:
强可到达
,如果从GC Root搜索后,发现对象与GC Root之间存在强引用链则为强可到达。强引用链即有强引用对象,引用了该对象。软可到达
,如果从GC Root搜索后,发现对象与GC Root之间不存在强引用链,但存在软引用链,则为软可到达。软引用链即有软引用对象,引用了该对象。弱可到达
,如果从GC Root搜索后,发现对象与GC Root之间不存在强引用链与软引用链,但有弱引用链,则为弱可到达。弱引用链即有弱引用对象,引用了该对象。虚可到达
,如果从GC Root搜索后,发现对象与GC Root之间只存在虚引用链则为虚可到达。虚引用链即有虚引用对象,引用了该对象。不可达
,如果从GC Root搜索后,找不到对象与GC Root之间的引用链,则为不可到达。
根据上图的例子中,可以得到以下的结论:
- Object A为
强可到达
,Object B也为强可到达
,虽然Object B对象被SoftReference Objcet E
引用但由于其还被Object A引用所以为强可到达
; - 而Object C和Object D为
弱引用达到
,虽然Object D对象被PhantomReference Objcet G引用但由于其还被Object C引用,而Object C又为弱引用达到,所以Object D为弱引用达到; - 而Object H与Object I是
不可到达
。 - 引用链的强弱有关系依次是 强引用 > 软引用 > 弱引用 > 虚引用,如果有更强的引用关系存在,那么引用链到达性,将由更强的引用有关系决定。
源码分析
类图
可以看到的是除了软引用(Soft Reference)
、弱引用(Weak Reference)
和虚引用(PhantomReference)
多了一个
FinalReference 这个引用而Finalizer 继承于它,所以可以知道 这个引用就是用来处理上面所说的finalizer()
方法的。
所有的子类引用都继承于 Reference
类,先来分析一下此类。
Reference
JVM在GC时如果当前对象只被Reference
对象引用,JVM会根据Reference具体类型与堆内存的使用情况决定是否把对应的Reference对象加入到一个由Reference构成的pending链表上,如果能加入pending链表JVM同时会通知ReferenceHandler
线程进行处理。ReferenceHandler线程是在Reference类被初始化时调用的,其是一个守护进程并且拥有最高的优先级。Reference类静态初始化块代码如下:
static {
//省略部分代码...
Thread handler = new ReferenceHandler(tg, "Reference Handler");
handler.setPriority(Thread.MAX_PRIORITY);
handler.setDaemon(true);
handler.start();
//省略部分代码...
}
ReferenceHandle线程
而ReferenceHandler
线程内部的run
方法会不断地从Reference构成的pending
链表上获取Reference
对象,如果能获取则根据Reference的具体类型进行不同的处理,不能则调用wait方法等待GC回收对象处理pending链表的通知。ReferenceHandler线程run方法源码:
public void run() {
//死循环,线程启动后会一直运行
while (true) {
tryHandlePending(true);
}
}
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
//上锁
synchronized (lock) {
//<1> 如果JVM 发现有满足回收的对象,则会加入Reference 中的 pending链表。
// 这里会进行判断,如果有则取出来 并在pending链表上断开
if (pending != null) {
r = pending;
//instanceof 可能会抛出OOME,所以在将r从pending链上断开前,做这个处理
c = r instanceof Cleaner ? (Cleaner) r : null;
//将将r从pending链上断开
pending = r.discovered;
r.discovered = null;
//<2> 目前还没有,则wait等待
} else {
//等待CG后的通知
if (waitForNotify) {
lock.wait();
}
//重试
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
//当抛出OOME时,放弃CPU的运行时间,这样有希望收回一些存活的引用并且GC能回收部分空间。同时能避免频繁地自旋重试,导致连续的OOME异常
Thread.yield();
//重试
return true;
} catch (InterruptedException x) {
//重试
return true;
}
//<3> 在上面的pending链表中取出来的结果,如果是Cleaner类型的Reference调用其clean方法并退出
if (c != null) {
c.clean();
return true;
}
ReferenceQueue<? super Object> q = r.queue;
//<4> 如果Reference有注册ReferenceQueue,则处理pending指向的Reference结点将其加入ReferenceQueue中
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
}
<1>
如果JVM 发现有满足回收的对象,则会加入Reference 中的 pending链表。 这里会进行判断,如果有则取出来 并在pending链表上断开<2>
目前pending链表上为空,则wait等待<3>
在上面的pending链表中取出来的结果,如果是Cleaner类型的Reference调用其clean方法并退出<4>
如果Reference有注册ReferenceQueue,则处理pending指向的Reference结点将其加入ReferenceQueue中
上面tryHandlePending
方法中比较重要的点是c.clean()
与q.enqueue
Cleaner#clean
方法用于完成清理工作q.enqueue
则是ReferenceQueue将被回收对象加入到对应的Reference列队中,等待其他线程的后继处理。
上面就是Reference的整理流程图,接下来回头看一下Reference类的属性、构造方法以及一些重要的方法
构造方法
//构造函数,指定引用的对象referent
Reference(T referent) {
this(referent, null);
}
//构造函数,指定引用的对象referent与注册的queue
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
enqueue 方法
在上面流程图中,如果Reference#tryHandlePending
从pending
链表中取出的Reference对象不是Clean
类型的,则调用此方法加入ReferenceQueue中,等待其他线程的后续处理。
//Reference
//将当前对象加入创建时注册的queue中
public boolean enqueue() {
return this.queue.enqueue(this);
}
//ReferenceQueue
//入列队enqueue方法,只被Reference类调用,也就是上面分析中ReferenceHandler线程为调用
boolean enqueue(Reference<? extends T> r) {
//获取同步对象lock对应的监视器对象
synchronized (lock) {
//获取r关联的ReferenceQueue,如果创建r时未注册ReferenceQueue则为NULL,同样如果r已从ReferenceQueue中移除其也为null
ReferenceQueue<?> queue = r.queue;
//判断queue是否为NULL 或者 r已加入ReferenceQueue中,是的话则入队列失败
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
//设置r的queue为已入队列
r.queue = ENQUEUED;
//如果ReferenceQueue头节点为null则r的next节点指向当前节点,否则指向头节点
r.next = (head == null) ? r : head;
//更新ReferenceQueue头节点
head = r;
//列队长度加1
queueLength++;
//为FinalReference类型引用增加FinalRefCount数量
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
//通知remove操作队列有节点
lock.notifyAll();
return true;
}
}
poll方法以及remove方法
ReferenceQueue有入队方法enqueue方法,必然也是有获取和删除方法。
public Reference<? extends T> poll() {
//头结点为null直接返回,代表Reference还没有加入ReferenceQueue中
if (head == null)
return null;
//获取同步对象lock对应的监视器对象
synchronized (lock) {
return reallyPoll();
}
}
//从队列中真正poll元素的方法
private Reference<? extends T> reallyPoll() {
Reference<? extends T> r = head;
//double check 头节点不为null
if (r != null) {
//保存头节点的下个节点引用
Reference<? extends T> rn = r.next;
//更新queue头节点引用
head = (rn == r) ? null : rn;
//更新Reference的queue值,代表r已从队列中移除
r.queue = NULL;
//更新Reference的next为其本身
r.next = r;
queueLength--;
//为FinalReference节点FinalRefCount数量减1
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(-1);
}
//返回获取的节点
return r;
}
return null;
}
public Reference<? extends T> remove(long timeout) throws IllegalArgumentException, InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("Negative timeout value");
}
//获取同步对象lock对应的监视器对象
synchronized (lock) {
//获取队列头节点指向的Reference
Reference<? extends T> r = reallyPoll();
//获取到返回
if (r != null) return r;
long start = (timeout == 0) ? 0 : System.nanoTime();
//在timeout时间内尝试重试获取
for (;;) {
//等待队列上有结点通知
lock.wait(timeout);
//获取队列中的头节点指向的Reference
r = reallyPoll();
//获取到返回
if (r != null) return r;
if (timeout != 0) {
long end = System.nanoTime();
timeout -= (end - start) / 1000_000;
//已超时但还没有获取到队列中的头节点指向的Reference返回null
if (timeout <= 0) return null;
start = end;
}
}
}
}
Reference 以及ReferenceQueue到这里是分析完了。
总结一下:
- JVM在GC时如果当前对象只被
Reference
对象引用,JVM会根据Reference具体类型与堆内存的使用情况决定是否把对应的Reference对象加入到一个由Reference构成的pending链表
上 - 如果能加入
pending
链表JVM同时会通知ReferenceHandler
线程进行处理。ReferenceHandler线程收到通知后会调用Cleaner#clean
或ReferenceQueue#enqueue
方法进行处理
现在就差一个Cleaner 还没有分析。来看一下Cleaner的实现
Cleaner 属性
//继承了PhantomReference类也就是虚引用,PhantomReference源码很简单只是重写了get方法返回null
public class Cleaner extends PhantomReference<Object> {
/* 虚队列,命名很到位。之前说CG把ReferenceQueue加入pending-Reference链中后,ReferenceHandler线程在处理时
* 是不会将对应的Reference加入列队的,而是调用Cleaner.clean方法。但如果Reference不注册ReferenceQueue,GC处理时
* 又无法把他加入到pending-Reference链中,所以Cleaner里面有了一个dummyQueue成员变量。
*/
private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue();
//Cleaner链表的头结点
private static Cleaner first = null;
//当前Cleaner节点的后续节点
private Cleaner next = null;
//当前Cleaner节点的前续节点
private Cleaner prev = null;
//真正执行清理工作的Runnable对象,实际clean内部调用thunk.run()方法
private final Runnable thunk;
//省略部分代码...
}
Cleaner 构造函数以及方法
//私有方法,不能直接new
private Cleaner(Object var1, Runnable var2) {
super(var1, dummyQueue);
this.thunk = var2;
}
//创建Cleaner对象,同时加入Cleaner链中。
public static Cleaner create(Object var0, Runnable var1) {
return var1 == null ? null : add(new Cleaner(var0, var1));
}
//头插法将新创意的Cleaner对象加入双向链表,synchronized保证同步
private static synchronized Cleaner add(Cleaner var0) {
if (first != null) {
var0.next = first;
first.prev = var0;
}
//更新头节点引用
first = var0;
return var0;
}
public void clean() {
//从Cleaner链表中先移除当前节点
if (remove(this)) {
try {
//调用thunk.run()方法执行对应清理逻辑
this.thunk.run();
} catch (final Throwable var2) {
//省略部分代码..
}
}
}
Cleaner.clean
中的run方法有具体的子类实现。
DirectByteBuffer 堆外内存回收
在创建DirectByteBuffer时我们实际是调用ByteBuffer#allocateDirect
方法,而其实现如下:
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
DirectByteBuffer(int cap) {
//省略部分代码...
try {
//调用unsafe分配内存
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
//省略部分代码...
}
//省略部分代码...
//<1> 前面分析中的Cleaner对象创建,持有当前DirectByteBuffer的引用
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
<1>
里面和DirectByteBuffer
堆外内存回收相关的代码便是Cleaner.create(this, new Deallocator(base, size, cap))
这部分。还记得之前说实际的清理逻辑是里面和DirectByteBuffer堆外内存回收相关的代码便是Cleaner里面的Runnable#run方法吗?直接看Deallocator.run方法源码:
public void run() {
if (address == 0) {
// Paranoia
return;
}
//通过unsafe.freeMemory释放创建的堆外内存
unsafe.freeMemory(address);
address = 0;
Bits.unreserveMemory(size, capacity);
}
- 创建DirectByteBuffer对象时会创建一个Cleaner对象,Cleaner对象持有了DirectByteBuffer对象的引用
- 当JVM在GC时,如果发现DirectByteBuffer被地方法没被引用了,JVM会将其对应的Cleaner加入到
pending-reference
链表中,同时通知ReferenceHandler
线程处理,ReferenceHandler
收到通知后,会调用Cleaner#clean
方法,而对于DirectByteBuffer创建的Cleaner对象其clean方法内部会调用unsafe.freeMemory
释放堆外内存。
WeakHashMap敏感内存回收
WeakHashMap实现上其Entry继承了WeakReference。只要发生GC 就会清除
//Entry继承了WeakReference, WeakReference引用的是Map的key
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
final int hash;
Entry<K,V> next;
/**
* 创建Entry对象,上面分析过的ReferenceQueue,这个queue实际是WeakHashMap的成员变量,
* 创建WeakHashMap时其便被初始化 final ReferenceQueue<Object> queue = new ReferenceQueue<>()
*/
Entry(Object key, V value,
ReferenceQueue<Object> queue,
int hash, Entry<K,V> next) {
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
//省略部分原码...
}
- 往
WeakHashMap
添加元素时,实际都会调用Entry
的构造方法,也就是会创建一个WeakReference
对象,这个对象的引用的是WeakHashMap
刚加入的Key - 而所有的WeakReference对象关联在同一个
ReferenceQueue
上。我们上面说过JVM在GC时,如果发现当前对象只有被WeakReference对象引用,那么会把其对应的WeakReference对象加入到pending-reference链表
上,并通知ReferenceHandler
线程处理 - 而
ReferenceHandler
线程收到通知后,对于WeakReference
对象会调用ReferenceQueue#enqueue
方法把他加入队列里面。 - WeakHashMap 则通过
expungeStaleEntries
方法不断的从ReferenceQueue中取出Reference,然后进行清除
private void expungeStaleEntries() {
//不断地从ReferenceQueue中取出,那些只有被WeakReference对象引用的对象的Reference
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
//转为 entry
Entry<K,V> e = (Entry<K,V>) x;
//计算其对应的桶的下标
int i = indexFor(e.hash, table.length);
//取出桶中元素
Entry<K,V> prev = table[i];
Entry<K,V> p = prev;
//桶中对应位置有元素,遍历桶链表所有元素
while (p != null) {
Entry<K,V> next = p.next;
//如果当前元素(也就是entry)与queue取出的一致,将entry从链表中去除
if (p == e) {
if (prev == e)
table[i] = next;
else
prev.next = next;
// Must not null out e.next;
//清空entry对应的value
e.value = null;
size--;
break;
}
prev = p;
p = next;
}
}
}
}
接下来分析一个 finalizer()
方法被调用的过程。
finalizer()方法被调用的过程
JVM 中的实现
//share/vm/oops/instanceKlass.cpp
instanceOop InstanceKlass::allocate_instance(TRAPS) {
bool has_finalizer_flag = has_finalizer(); // Query before possible GC
int size = size_helper(); // Query before forming handle.
KlassHandle h_k(THREAD, this);
instanceOop i;
i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL);
//<1> 如果该类复写 Object.finalizer() 方法 则把_has_finalizer_flag置为true
if (has_finalizer_flag && !RegisterFinalizersAtInit) {
i = register_finalizer(i, CHECK_NULL);
}
return i;
}
//然后会调用register_finalizer
instanceOop InstanceKlass::register_finalizer(instanceOop i, TRAPS) {
if (TraceFinalizerRegistration) {
tty->print("Registered ");
i->print_value_on(tty);
tty->print_cr(" (" INTPTR_FORMAT ") as finalizable", (address)i);
}
instanceHandle h_i(THREAD, i);
// Pass the handle as argument, JavaCalls::call expects oop as jobjects
JavaValue result(T_VOID);
JavaCallArguments args(h_i);
//<2> 这里就是调用Java里面Finalizer 类中的 register 方法
methodHandle mh (THREAD, Universe::finalizer_register_method());
JavaCalls::call(&result, mh, &args, CHECK_NULL);
return h_i();
}
- <1> 在上面的JVM 代码中
//share/vm/oops/instanceKlass.cpp
在解析 class 的时候,如果这个类复写了 Object里面的finalizer方法的话,会把_has_finalizer 置为true 并且调用register_finalizer()
方法 - <2> 这里会调用 Java里面Finalizer 类中的
register
方法
到这里就是JVM 发现如果一个类复写了finalizer方法就会调用Finalizer类里面的register方法,接下来看一下Finalizer类是如何实现的
Finalizer.register( )
/* Invoked by VM */
static void register(Object finalizee) {
//<1> 创建实例
new Finalizer(finalizee);
}
private Finalizer(Object finalizee) {
//<2> 放入父类中的ReferenceQueue中(Reference类)
super(finalizee, queue);
//<3> 所有的复写finalizer 方法的类组成一个双向链表 叫unfinalized
add();
}
private void add() {
synchronized (lock) {
if (unfinalized != null) {
this.next = unfinalized;
unfinalized.prev = this;
}
unfinalized = this;
}
}
<1>
可以看到Finalizer.register()
方法中创建Finalizer
的实例, Finalizer 类中的静态代码块。<2>
放入父类中的ReferenceQueue中(Reference类)<3>
所有的复写finalizer 方法的类组成一个双向链表 叫unfinalized
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
//创建FinalizerThread 线程
Thread finalizer = new FinalizerThread(tg);
finalizer.setPriority(Thread.MAX_PRIORITY - 2);
finalizer.setDaemon(true);
finalizer.start();
}
private static class FinalizerThread extends Thread {
private volatile boolean running;
FinalizerThread(ThreadGroup g) {
super(g, "Finalizer");
}
public void run() {
// in case of recursive call to run()
if (running)
return;
// Finalizer thread starts before System.initializeSystemClass
// is called. Wait until JavaLangAccess is available
while (!VM.isBooted()) {
// delay until VM completes initialization
try {
VM.awaitBooted();
} catch (InterruptedException x) {
// ignore and continue
}
}
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
//死循环调用 runFinalizer方法
for (;;) {
try {
Finalizer f = (Finalizer)queue.remove();
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
}
}
}
}
private void runFinalizer(JavaLangAccess jla) {
synchronized (this) {
if (hasBeenFinalized()) return;
remove();
}
try {
Object finalizee = this.get();
if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
jla.invokeFinalize(finalizee);
/* Clear stack slot containing this variable, to decrease
the chances of false retention with a conservative GC */
finalizee = null;
}
} catch (Throwable x) { }
super.clear();
}
可以看到在Finalizer
类中的静态代码块中创建了FinalizerThread
线程,在线程中又死循环从ReferenceQueue中不断取Reference对象,取到之后调用invokeFinalize
方法,这此方法就是调用该类的 finalizer
方法。
总结
- 创建对象时,如果对象
override
了finalize()
方法,jvm会同时创建一个Finalizer
对象 - 所有Finalizer对象组成了一个
双向链表
- 所有Finalizer对象都有一个名为queue的成员变量,指向的都是Finalizer类的静态Queue。
- cms gc执行到mark阶段的最后时,会把需要gc的对象加入到Reference的pending list中。
- 有一个专门的高级别线程
Reference Handler
处理pending list
,把pending list中的对象取出来,放到这个对象所指的Reference Queue中,对于Finalizer对象来说, 这个queue指向Finalizer类的静态Queue。 - Finalizer类有一个专门的线程负责从queue中取对象,并且执行finalizer引用的对象的finalize函数。 根据cms来将如果一个对象
override finalize()
方法,那么并不意味着这个对象的会立马被gc给回收,在cms中会进行remark 重新标记,如果该对象原本是要被回收的但是又被引用了那么它就不会被回收,而finalize只会被调用一次,因为前面已经调用过了,所以不会在调用finalize方法,而产生问题。 DirectByteBuffer
创建实例的时候会创建Cleaner,通过Cleaner#run
方法进行回收内存- WeakHashMap 操作
expungeStaleEntries
方法,不断的从ReferenceQueue中获取然后进行清除对应的Entry。
参考
《深入理解Java虚拟机》