Java引用类型(Reference)

本文深入探讨了Java中四种引用类型(强引用、软引用、弱引用、虚引用)的概念及应用场景,通过实例演示了每种引用类型的行为特征,并介绍了引用队列的作用。

        GC的基本思想是考察每个对象的可触及性(可达性),就是从GC Root开始是否可以访问到这个对象。如果可以,则可达,否则就是不可达。在Java中,可作为GC Roots的对象包括:

        JVM栈(栈中的本地变量表)中的引用的对象

        方法去中类静态属性引用的对象

        方法区中常量引用的对象

        本地方法栈中JNI(Native方法)引用的对象

        对于可触及性,可以包含以下3种状态。

        可触及的:从根节点开始,可以到达这个对象。

        可复活的:对象的所有引用都被释放,但是对象有可能在finalize()函数中复活
        不可触及的:对象的finalize()方法别调用过,并且没有被复活(finalize()方法只会被调用一次)
        Java中提供了4个级别的引用:强引用、软引用、弱引用和虚引用。
强引用
        强引用是程序中一般使用的引用类型。强引用的对象是可触及的、不可被回收的。例如:

StringBuffer str = new StringBuffer("Hugege");
StringBuffer str1 = str;

        上述代码内存示意图如下:


        强引用包含以下特点:
        可以直接访问目标对象
        所指向的对象在恩和时候都不会被系统回收,JVM宁愿抛出OOM,也不会回收对象
        可能导致内存泄漏

软引用
        软引用是比强引用弱一点的引用类型。一个对象如果只持有软引用,那么当堆空间不足时,就会被回收(由SoftReference实现)。代码如下:

/**
 * 软引用在系统堆内存不足是,被回收
 * 
 * VM Args: -Xmx10M
 * 
 * @author xuefeihu
 *
 */
public class SoftRef {

	public static class User {
		public int id;
		public String name;

		public User(int id, String name) {
			this.id = id;
			this.name = name;
		}

		@Override
		public String toString() {
			return "User [id=" + id + ", name=" + name + "]";
		}
	}

	public static void main(String[] args) {
		User u = new User(1, "xuefeihu");
		SoftReference<User> userSoftRef = new SoftReference<User>(u);
		u = null;

		System.out.println(userSoftRef.get());
		System.gc();
		System.out.println("After GC: ");
		System.out.println(userSoftRef.get());

		byte[] b = new byte[1024 * 985 * 7]; // 这里与JDK的版本有关(笔者JDK8),过小不会回收,过大会OOM
		System.gc();
		System.out.println(userSoftRef.get());

	}

}

        增加上述GC参数,运行结果如下:

User [id=1, name=xuefeihu]
After GC:                    //内存充足时GC
User [id=1, name=xuefeihu]
null                         //内存不足是GC,数据已被清除
        综上,GC未必会回收软引用的对象,但是当内存不足时,软引用对象就会被回收,因此软引用对象不会引起内存溢出。
引用队列
        每个软引用都可以附带一个引用队列,当对象的可达性状态发生变化时(由可达变为不可达),软引用对象就会进入引用队列。通过这个队列,可以追踪对象的回收情况。代码如下:

/**
 * 每个软引用都可以附带一个引用队列,当对象的可达性状态发生变化时(由可达变为不可达),
 * 软引用对象就会进入引用队列。通过这个队列,可以追踪对象的回收情况。
 * 
 * VM Args: -Xmx10M
 * 
 * @author xuefeihu
 *
 */
public class SoftRefQ {

	public static class User {
		public int id;
		public String name;

		public User(int id, String name) {
			this.id = id;
			this.name = name;
		}

		@Override
		public String toString() {
			return "User [id=" + id + ", name=" + name + "]";
		}
	}

	static ReferenceQueue<User> softQueue = null;

	public static class CheckRefQueue extends Thread {
		@Override
		public void run() {
			while (true) {
				if (softQueue != null) {
					UserSoftReference obj = null;
					try {
						obj = (UserSoftReference) softQueue.remove();
					} catch (Exception e) {
						e.printStackTrace();
					}
					if (obj != null) {
						System.out.println("user id = " + obj.uid + " is delete");
					}
				}
			}
		}
	}

	public static class UserSoftReference extends SoftReference<User> {
		int uid;

		public UserSoftReference(User referent, ReferenceQueue<? super User> q) {
			super(referent, q);
			uid = referent.id;
		}
	}

	public static void main(String[] args) throws InterruptedException {
		Thread t = new CheckRefQueue();
		t.setDaemon(true);
		t.start();
		User u = new User(1, "xuefeihu");
		softQueue = new ReferenceQueue<User>();
		UserSoftReference userSoftRef = new UserSoftReference(u, softQueue);
		u = null;
		System.out.println(userSoftRef.get());
		System.gc();
		// 内存足够,不会被回收
		System.out.println("After GC :");
		System.out.println(userSoftRef.get());

		System.out.println("try to create byte array and GC");
		byte[] b = new byte[1024 * 985 * 7];
		System.gc();
		System.out.println(userSoftRef.get());

		Thread.sleep(1000);
	}

}

        使用上述VM参数,执行结果如下:

User [id=1, name=xuefeihu]      
After GC :
User [id=1, name=xuefeihu]          //内存充足时,GC结果
try to create byte array and GC     //内存不足时GC
user id = 1 is delete               //引用队列探测到对象被删除
null                                //对象已回收

弱引用
        弱引用是一种比软引用还弱的一种引用。GC时只要发现,就会对其进行回收。一旦被回收时,就会加入到一个引用队列中(和软引用很像),Java中使用WeakReference实现。demo如下:

/**
 * 弱引用在GC时就会被回收
 * 
 * @author xuefeihu
 *
 */
public class WeakRef {

	public static class User {
		public int id;
		public String name;

		public User(int id, String name) {
			this.id = id;
			this.name = name;
		}

		@Override
		public String toString() {
			return "User [id=" + id + ", name=" + name + "]";
		}
	}

	public static void main(String[] args) {
		User u = new User(1, "xuefeihu");
		WeakReference<User> userWeakRef = new WeakReference<User>(u);
		u = null;

		System.out.println(userWeakRef.get());
		System.gc();
		// 不管当前内存空间足够与否,都会回收它的内存
		System.out.println("After GC: ");
		System.out.println(userWeakRef.get());

	}

}

        上述代码运行结果如下:

User [id=1, name=xuefeihu]
After GC: 
null

        从结果看来,不论内存状况,每次GC都会清除弱引用的对象。
虚引用(幽灵引用)
        虚引用是所有引用中最弱的一个。持有虚引用的对象,和没有引用几乎一样,随时都有可能被回收,因此也叫幽灵引用。当使用虚引用的get()方法获得强引用时,总是失败的。虚引用必须和引用队列一起使用,作用于跟踪垃圾回收过程。
        下面示例使用虚引用跟踪一个可复活对象的回收。

/**
 * 使用虚引用跟踪一个可复活对象的回收
 * 
 * 当GC回收对象时,如果发现有虚引用,就会将其放入引用队列
 * 虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。
 * 
 * @author xuefeihu
 *
 */
public class TraceCanReliveObj {
	
	public static TraceCanReliveObj obj;
	static ReferenceQueue<TraceCanReliveObj> phantomQueue = null;
	
	public static class CheckRefQueue extends Thread {
		@Override
		public void run() {
			while(true) {
				if(phantomQueue != null) {
					PhantomReference<TraceCanReliveObj> objt = null;
					try {
						objt = (PhantomReference<TraceCanReliveObj>) phantomQueue.remove();
					} catch (Exception e) {
						e.printStackTrace();
					}
					if(objt != null) {
						System.out.println("TraceCanReliveObj is delete by GC");
					}
				}
			}
		}
	}
	
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println("CanReliveObj finalize called");
		obj = this;
	}
	
	@Override
	public String toString() {
		return "I am CanReliveObj";
	}
	
	public static void main(String[] args) throws InterruptedException {
		Thread t = new CheckRefQueue();
		t.setDaemon(true);
		t.start();
		
		phantomQueue = new ReferenceQueue<TraceCanReliveObj>();
		obj = new TraceCanReliveObj();
		PhantomReference<TraceCanReliveObj> phantomRef = new PhantomReference<TraceCanReliveObj>(obj, phantomQueue);
		
		obj = null;
		System.gc();
		Thread.sleep(1000);
		if(obj == null) {
			System.out.println("obj 是 null");
		} else {
			System.out.println("obj 可用");
		}
		
		System.out.println("第2次GC");
		obj = null;
		System.gc();
		Thread.sleep(1000);
		if(obj == null) {
			System.out.println("obj 是 null");
		} else {
			System.out.println("obj 可用");
		}
		
	}

}

        上述代码执行结果如下:

CanReliveObj finalize called         // 对象复活
obj 可用
第2次GC                               //对象无法复活
TraceCanReliveObj is delete by GC    //引用队列捕获到对象被回收
obj 是 null

参考:《实战Java虚拟机》


链接:http://moguhu.com/article/detail?articleId=52

### Java 引用类型的详细介绍 Java 中的引用类型可以分为四种主要类别:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)以及引用(Phantom Reference)。这些引用类型在不同的场景下具有各自的作用。 #### 强引用(Strong Reference强引用Java 中最常见的一种引用类型。当一个对象通过 `new` 关键字创建并赋值给某个变量时,默认情况下会形成一种强引用关系。只要存在强引用,垃圾回收器不会回收该对象所占用的内存资源[^2]。 例如: ```java String str = new String("Hello"); ``` 在此例子中,`str` 是对字符串 `"Hello"` 的强引用。只有当所有的强引用都被移除或者置为 `null` 后,垃圾回收器才会考虑回收此对象。 --- #### 软引用(Soft Reference软引用适用于那些可能需要被释放但又希望尽可能长时间保留的对象。通常来说,软引用指向的对象会在 JVM 内存不足的情况下被垃圾回收器回收。这种机制非常适合实现缓存功能,在内存紧张时自动清理不再使用的对象以节省空间[^3]。 要使用软引用,可以通过 `java.lang.ref.SoftReference` 类来定义: ```java import java.lang.ref.SoftReference; // 创建一个 SoftReference 对象 SoftReference<String> softRef = new SoftReference<>(new String("Soft Referenced Object")); System.out.println(softRef.get()); // 输出: Soft Referenced Object ``` 需要注意的是,一旦 JVM 面临内存压力,上述软引用可能会被清除掉。 --- #### 弱引用(Weak Reference) 与软引用不同,弱引用指向的对象即使还有其他弱引用的存在也会随时被垃圾回收器回收。因此,弱引用适合于构建一些临时性的映射表结构,比如事件监听器列表等。一旦没有其他的强引用维持住目标对象,则其生命周期完全依赖于 GC 行动计划。 以下是关于如何利用 `java.lang.ref.WeakReference` 来管理对象的一个简单示例: ```java import java.lang.ref.WeakReference; // 定义 WeakReference 实例 WeakReference<String> weakRef = new WeakReference<>(new String("Weakly referenced object")); // 访问原始对象 if (weakRef.get() != null){ System.out.println(weakRef.get()); } else { System.out.println("Object has been garbage collected."); } ``` --- #### 引用(Phantom Reference引用与其他几种引用方式相比更为特殊,因为它们并不真正持有任何实际的数据内容;相反,它们仅能用来监控某特定时刻——即关联实体即将由GC销毁之前的通知消息。换句话说,我们无法经由引用来重新获得已消亡的目标实例本身。为了能够有效运用此类特性,往往还需要借助 `ReferenceQueue` 结合操作才行。 下面是一个简单的演示代码片段: ```java import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; public class PhantomExample { public static void main(String[] args) throws InterruptedException { Object obj = new Object(); ReferenceQueue<Object> refQueue = new ReferenceQueue<>(); // 建立 PhantomReference 并注册至队列 PhantomReference<Object> phantomRef = new PhantomReference<>(obj, refQueue); obj = null; // 断开强引用 while(true){ if(refQueue.poll()!=null){ System.out.println("The object is about to be finalized and removed by the Garbage Collector!"); break; } Thread.sleep(100); // 等待一段时间观察状态变化 } } } ``` --- ### 总结 每种引用类型都有自己的适用范围和特点。开发者可以根据具体需求选择合适的引用形式,从而更好地控制应用程序内的内存分配策略及其优化效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值