Java Reference概念整理

本文深入探讨 Java 中引用类型的原理与应用,包括强引用、弱引用、软引用和幻影引用的概念、作用与实例代码解析,以及如何利用 ReferenceQueue 实现事件回调。

Strong Reference, 强引用,即java标准的引用方式,表示GC从 Root Set 开始向下扫描,可以找到对应的 Strong Reference。

Referent,被包装为 Weak, Soft, Phantom Reference的对象引用称之为 referent。后面的内容会多次提到这个名词。

Weak Reference, 弱引用。当一个referent,在运行时没有同时被强,软引用,只被Weak Reference自身引用,且Weak Reference从 Root Set 可达,则该referent会被GC回收。

WR的作用,一般是为referent提供一个被回收的凭据,结合ReferenceQueue可以让程序在第一时间得到referent被回收的事件,从而做一些额外的clean操作。(如果对ReferenceQueue作用和回调感兴趣,可以先看最下面的 ReferenceQueue 简介)

Soft Reference, 软引用。它是除strong外,生命周期最长的一种 Reference,只有当JVM Heap中充满Strong References, Full GC无法为heap腾出更多空间而即将抛出OOM时,SoftReferences会被GC回收。

SR的作用一般是用作不限大小的 cache(无需remove)。
比如将其 Soft Reference 无限地放入 hashmap 中,而不关心hashmap内的对象数量是否会撑爆heap,也不需要手动 remove。
当Heap容量达到OOM触发条件时,VM会自动回收该map里的所有SoftReferences.

Phanton Reference, 是一种特殊的Reference,正如他的名字所表达的,幻影引用,他可以像幻影一样附着在referent上。
当GC在遍历引用关系时,如果发现被phantom reference包装过的referent不存在strong, weak, soft引用时(就是除phantom外没有任何引用,幻影的由来),GC会将 phantom reference 放入 Reference queue。以便程序在另一边通过queue的remove/poll方法,感知referent被GC回收的事件。(如果对ReferenceQueue作用和回调感兴趣,可以先看最下面 ReferenceQueue 简介)

另外,我们知道,GC在回收对象前会先调用对象自身的finalize()方法,如果它有实现的话,然后再清掉内存。而Phantom Reference的回调(enqueue)是在对象的finalize后,回收前触发。这跟 WeakReference不一样。WR是在回收后才通知的。在这个特殊的阶段可以做一些特殊的clean操作。

为什么 Phantom Reference 的get总是返回null?
因为phantom reference想做到幻影(除自身外,不跟其他任何引用有关联),所以不允许程序能通过自身的get方法得到referent,而破坏幻影的初衷。

实例代码

package com.kenwublog.reference;
 
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.HashMap;
 
public class PhantomReferenceTest {
 
	public static void main(String[] args) {
		ReferenceQueue referenceQueue = new ReferenceQueue();
		Object object = new Object() {
			public String toString() {
				return "Referenced Object";
			}
		};
 
		Object data = new Object() {
			public String toString() {
				return "Data";
			}
		};
 
		HashMap map = new HashMap();
		Reference reference = null;
		System.out.println("Testing PhantomReference.");
		reference = new PhantomReference(object, referenceQueue);
 
		map.put(reference, data);
 
		System.out.println(reference.get()); // null
		System.out.println(map.get(reference)); // Data
		System.out.println(reference.isEnqueued()); // false
 
		System.gc();
		System.out.println(reference.get()); // null
		System.out.println(map.get(reference)); // Data
		System.out.println(reference.isEnqueued()); // false
 
		object = null;
		data = null;
 
		System.gc();
		System.out.println(reference.get()); // null
		System.out.println(map.get(reference)); // Data
		System.out.println(reference.isEnqueued()); // true, because object has been reclaimed.
	}
 
}

ReferenceQueue,一种当 weak, soft, phantom的referent被GC回收后,提供事件回调的接口。需要在实例化三大reference时,通过构造函数传入,phantom reference是强制需要传入的,weak和soft可不传。

回调过程:

GC回收referent后(phantom是在回收前,finalize后),将reference enqueue到RQ中,程序通过调用RQ的remove方法来感知reference被GC回收的事件。
remove方法是阻塞的,当没有referent被回收时(GC未调用enqueue),remove方法会一直挂起线程,当有referent被回收时,该方法返回 referent对应的reference对象。
同样,RQ也提供了一个非阻塞的方法 poll,但这样就做不到实时回调了。

实例

package com.kenwublog.reference;
 
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;
 
public class ReferenceQueueTest {
 
	public static void main(String[] args) {
		final ReferenceQueue q = new ReferenceQueue();
		String str = new String("AK47");
		WeakReference wr = new WeakReference(str, q);
 
		Thread t = new Thread(){
			@Override
			public void run() {
				try {
					Reference reference = q.remove();
					System.out.println(reference + " event fired.");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		};
		t.setDaemon(true);
		t.start();
		System.out.println("Reference Queue is listening.");
 
		str = null; // clear strong reference
		System.out.println("Ready to gc");
		System.gc();
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("wr.get: " + wr.get());
	}
 
}

参考资料

http://mindprod.com/jgloss/phantom.html (References的目的,在GC的哪种阶段触发对比表格)
http://www.pawlan.com/monica/articles/refobjs/

<think>嗯,用户问的是Java中的引用计数器的工作原理和计数方式。首先,我需要回忆一下引用计数法的基本概念。记得引用计数法是一种垃圾回收算法,每个对象有一个计数器,记录有多少引用指向它。当引用数为零时,对象就会被回收。不过,Java主要用的不是引用计数法,而是可达性分析算法。用户可能看到了一些资料提到Java的引用计数器,但可能混淆了不同的概念。 接下来,要确认Java是否真的使用引用计数法。根据引用的资料,引用[2]提到Java引入了垃圾回收机制,但引用计数法可能不是主要方法。引用[3]和[4]指出Java虚拟机使用可达性分析,而引用计数法有循环引用的问题,所以可能不被Java采用。用户提供的引用中,引用[2]虽然提到引用计数算法,但可能是指其他语言或者早期的Java版本?需要进一步核实。 然后,需要解释引用计数器的工作原理。即使Java不主要使用它,用户还是想知道它的机制。应该详细说明引用计数如何增加和减少,比如创建新引用时计数器加1,引用失效时减1,当为0时回收内存。同时要指出Java没有采用的原因,比如循环引用的问题。 另外,用户提供的引用[4]提到软引用和System.gc(),这可能和Java的四种引用类型有关(强引用、软引用、弱引用、虚引用)。需要区分引用计数算法和Java中的引用类型,避免混淆。用户可能将两者混为一谈,需要明确说明。 还要考虑用户可能的误解,比如认为Java使用引用计数法,但实际上使用的是可达性分析。需要澄清这一点,并引用正确的资料,如引用[3]和[4]提到的GC关注堆和方法区,使用可达性分析。 最后,生成相关问题时要覆盖引用计数法的优缺点、Java实际使用的GC机制、循环引用问题以及不同引用类型的作用。确保回答结构清晰,先说明引用计数器原理,再指出Java实际使用的方法,避免用户被误导。</think>### Java引用计数器的工作原理及计数机制分析 Java中的引用计数器是垃圾回收机制中的一种理论模型,但需特别注意:**主流的Java虚拟机(JVM)并未采用引用计数法作为主要垃圾回收策略**[^3]。以下是其原理和实现细节的详细说明: #### 一、引用计数器的核心机制 1. **基础原理** - 每个对象内置一个计数器字段,记录当前指向它的引用数量 - 当新引用建立时执行`计数器+1`操作,例如: ```java Object a = new Object(); // 计数器初始化为1 Object b = a; // 计数器+1 → 当前值2 ``` - 当引用失效时执行`计数器-1`操作: ```java b = null; // 计数器-1 → 当前值1 a = null; // 计数器-1 → 当前值0 → 触发回收 ``` 2. **内存回收条件** - 满足`计数器 == 0`时立即回收对象内存 - 此过程不需要等待GC周期,理论上具有实时性 #### 二、Java未采用引用计数的原因 1. **循环引用缺陷** $$ \begin{cases} A.instance = B \\ B.instance = A \\ A = null \\ B = null \end{cases} \Rightarrow \text{计数器永远≥1} \Rightarrow \text{内存泄漏} $$ 该场景在可达性分析算法中可通过GC Roots遍历正确识别[^3] 2. **性能损耗问题** - 频繁更新计数器导致额外计算开销 - 多线程环境需要原子操作保证线程安全 #### 三、Java的实际实现方式 1. **可达性分析算法** - 通过GC Roots(栈帧局部变量、静态变量等)作为起点 - 构建对象引用链进行可达性标记[^3] 2. **分代回收策略** - 新生代使用复制算法 - 老年代使用标记-整理/清除算法 #### 四、Java中的特殊引用类型 虽然不使用引用计数,但Java提供四种引用类型: | 引用类型 | 回收条件 | 典型应用 | |----------|----------|----------| | 强引用 | 永不回收 | 普通对象 | | 软引用 | 内存不足 | 缓存系统[^4] | | 弱引用 | 下次GC时 | WeakHashMap | | 虚引用 | 随时回收 | 资源清理 | ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值