【♨ Java基础】虚引用(PhantomReference)的使用场景
虚引用是最弱的一种java对象引用方式,其他的引用方式至少还能get到对象,而虚引用的句柄是获取不到对象的,正如它的名字一样:形同虚设。
使用
public class TestMain {
public static void main(String[] args) {
// 新建一个对象
User obj = new User();
// 存储被回收的对象
ReferenceQueue<User> QUEUE = new ReferenceQueue<>();
// phantomReference使用虚引用指向这个内存空间
PhantomReference<User> phantomReference = new PhantomReference<>(obj, QUEUE);
System.out.println(phantomReference.get()); // 获取不到 打印为null
}
static class User {
}
}
作用
这个虚引用连获取都获取不到,有什么用呢?创建虚引用时传入的队列QUEUE又有什么用呢?
虚引用的作用就是在对象被GC回收时能得到通知
如何通知呢?就是在对象被回收后,把它的弱引用对象(PhantomReference)存入QUEUE对列中,这样我们查看队列就可以得知某个对象被GC回收了
// 新建一个对象,开辟一个内存空间
User obj = new User();
// 存储被回收的对象的虚引用对象
ReferenceQueue<User> QUEUE = new ReferenceQueue<>();
// phantomReference使用虚引用指向这个内存空间
PhantomReference<User> phantomReference = new PhantomReference<>(obj, QUEUE);
// 释放这个内存空间,此时只剩phantomReference通过虚引用指向它
obj = null;
// 调用gc回收new User的内存空间
System.gc();
// 被清除的队列中取出被回收的对象
while (true) {
Reference<? extends User> poll = QUEUE.poll();
if (poll!=null) {
System.out.println("--obj is recovery--");
break;
}
}
最终输出“--obj is recovery--”,即我们得到了对象被GC的消息。
其实,虚引用的存在意义就是监控对象是否存活。
场景
什么场景下我们可以考虑使用虚引用呢?
比如我们某个对象映射到代码外的一个实际资源,那么一般我们在创建对象时会同时创建这个资源,当然希望在对象被销毁前释放或删除资源,由于手动删除总会可能出现忘记的情况,这时我们就希望在对象销毁时得到通知,如果忘记手动销毁则自动销毁。
MySQLl驱动使用虚引用
以MySQL为例,MySQL驱动当创建了Connection对象时,会对应生成一个客户端和数据库服务的真实存在的网络连接,当查询完数据后,可以通过connection.close()方法关闭这个连接,但是如果忘了写这句关闭的代码难道就永远保持连接吗?那必然是不行的,所以MySQL驱动使用了虚引用,在Connection对象被垃圾回收时,自动执行关闭网络连接的方法。
写个例子
场景:我们的每创建一个User对象对应在数据库中生成一条数据,当对象销毁时删除这条数据
用户类代码如下:
/**
* 用户类
*/
static class User {
public DatabaseClient databaseClient;
public User() {
// 初始化客户端
databaseClient = new DatabaseClient();
// 创建时数据库创建数据
this.databaseClient.create();
}
}
/**
* 数据库客户端
*/
static class DatabaseClient {
/**
* 创建用户数据
*/
public void create() {
System.out.println("--数据库创建用户数据--");
}
/**
* 删除用户数据
*/
public void remove() {
System.out.println("--数据库删除用户数据--");
}
}
此时如果运行如下代码:
public static void main(String[] args) {
User obj = new User();
// 释放这个内存空间
obj = null;
// 调用gc
System.gc();
}
输出:--数据库创建用户数据--
说明代码运行结束,user被回收了,数据库中的数据还是存在(没有调用remove方法),显然不符合需求,此时虚引用就可以上线了。
首先继承一下虚引用的类:
static class UserPhantomReference extends PhantomReference<User> {
// 保存user的databaseClient 因为取不到user对象
public DatabaseClient databaseClient;
public UserPhantomReference(User referent, ReferenceQueue<? super User> q) {
super(referent, q);
this.databaseClient = referent.databaseClient;
}
}
这样主要是为了可以在对象删除时获取到databaseClient,才能实际删除数据,因为默认的PhantomReference是get不到数据的。
public static void main(String[] args) {
// 新建一个对象,开辟一个内存空间
User obj = new User();
// 存储被回收的对象
ReferenceQueue<User> QUEUE = new ReferenceQueue<>();
// phantomReference使用虚引用指向这个内存空间
UserPhantomReference phantomReference = new UserPhantomReference(obj, QUEUE);
// 释放这个内存空间,此时只有phantomReference通过虚引用指向它
obj = null;
// 调用gc
System.gc();
// 被清除的队列中取出被回收的对象,一般新开一个线程来监控
while (true) {
Reference<? extends User> poll = QUEUE.poll();
if (poll!=null) {
UserPhantomReference userPhantomReference = (UserPhantomReference) poll;
// 对象被回收,删除对应数据
userPhantomReference.databaseClient.remove();
System.out.println("--obj is recovery--");
}
}
}
此时输出:
--数据库创建用户数据--
--数据库删除用户数据--
说明对象在被GC回收时,对应的数据库数据也删掉了,满足了我们的需求。
finalize
提到虚引用,就不得不提finalize方法,finalize方法也是在对象回收时被执行,通过实现finalize方法上面的需求很容易就搞定了,只需要User对象重写finalize方法即可。
static class User {
public DatabaseClient databaseClient;
public User() {
// 初始化客户端
databaseClient = new DatabaseClient();
// 创建时数据库创建数据
this.databaseClient.create();
}
@Override
protected void finalize() {
// 销毁时删除数据
this.databaseClient.remove();
}
}
此时运行代码如下:
public static void main(String[] args) {
User obj = new User();
// 释放这个内存空间
obj = null;
// 调用gc
System.gc();
}
此时输出:
--数据库创建用户数据--
--数据库删除用户数据--
结果完全一致,而且对调度的代码几乎零侵入。
那么finalize这么好用,写法这么简单,为什么还要有虚引用呢?
道理其实很简单,好用一般都不灵活,就像synchronized锁一样,用起来简单,但运行的规矩你是改不了的。
同样finalize主要有以下几点不灵活:
- finalize方法执行的线程是不可控的
- finalize方法的执行是串行执行的,使用虚引用我们可以并行多线程执行
- finalize降低了gc效率,而虚引用不影响(实际上finalize执行时对象还没销毁)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本