这是我参与更文挑战的第29天,活动详情查看: 更文挑战
前言
在学习 Java 语言的时候,我们知道 Java 语言是强类型语言,它对每一种数据都定义了明确的具体数据类型,在内存中分配了不同大小的内存空间。其中基本类型共有 8 种,基本数据类型存储的是值,int 、char、float等,引用类型有 3 种,包括类(class)、接口(interface)和数组(array),引用类型存储的是地址。
所有的基本类型赋值都是按值传递,而引用类型是按引用传递。今天,我们就来谈谈 Java 的对象引用。
一、对象与引用
类比 c++ 中的指针指向某块内存空间,Java 则通过引用来完成对象地址的操作。我们通常在一定的内存空间(栈空间)来保存这种引用类型,再用堆空间中保存引用的对象,如下图:
a 和 b 是不同的引用,引用了不同的对象 ”hello“ 和 ”hello world“ 。
我们也可以改变引用指向的对象,同时,一个对象也支持多个引用。
二、四种引用
为什么 java 中会有四种引用类型? 我们知道 JVM 的GC 机制它有引用计数法,如果某个对象被引用了,则不可回收,在 JDK1.2 之前,一个对象只有 ”已被引用“ 和 ”未被引用“ 的两种状态,在内存不够用的时候,如果能回收部分被引用的内存,就可以达到性能上的提升的需求。
Java 为引用类型专门定义了一个类叫做 Reference 。在JDK1.2 之后,把对象的引用分为四种状态,即强引用、软引用、弱引用 和虚引用。这样的方式,可以更加灵活地控制对象的生命周期。其继承关系如图:
这非常好的比喻概括了这四种引用的关系,借鉴一下:
1、强引用就像大老婆,关系很稳固。
2、软引用就像二老婆,随时有失宠的可能,但也有扶正的可能。
3、弱引用就像情人,关系不稳定,可能跟别人跑了。
4、虚引用就是梦中情人,只在梦里出现过。
— 来自《Java 核心技术36 讲》海怪哥哥 的留言
2.1 强引用 Strong Reference
Java 中的 强引用是指 能够通过 GC root 对象直接找到的对象,那么这个对象无论内存是否足够,都不会被回收。
强引用在程序代码中普遍存在,即时内存不足,JVM 宁愿抛出 OutOfMemory 错误也不会回收对象,比如下面这段:
String a="hello world";
Object o =new Object();
复制代码
不过,如果超出局部变量的作用范围,或者将强引用赋值为null,或调用clear()方法,那么就可以完成对象回收。
2.2 软引用 Soft Reference
软引用的意思是只有在内存不足的情况下,被引用的对象才会被回收。
注意,是内存不足才回收,内存充足的时候不会回收。软引用通常用来实现内存敏感的缓存,例如加载文章的时候,如果还有多余内存,就把图片加载出来,如果内存紧张,就不加载图片的缓存。
public class test {
public static void main(String[] args) {
SoftReference<String> str = new SoftReference<String>("hello world");
System.out.println(str.get());
System.gc();
System.out.println(str.get());
}
}
复制代码
2.3 弱引用 weak Reference
弱引用的等级更低,无论内存死否足够,只要开启进行垃圾回收的时候,被弱引用的对象都会被回收
public class test {
public static void main(String[] args) {
WeakReference<String> str = new WeakReference<>(new String("hello world"));
System.out.println(str.get());
System.gc();
System.out.println(str.get());
}
}
复制代码
输出结果:
hello world
null
复制代码
2.4 虚引用 Phantom Reference
虚引用是最弱的一种引用,它随时可能被回收,且我们不能通过它访问引用对象,
通常,为一个对象设置虚引用的唯一目的就在于跟踪垃圾回收过程,例如,如果这个对象被回收时候能够收到一个系统的通知。
查看该类的源码,发现它只有一个构造函数和一个get() 方法,而且它的get()方法仅仅是返回一个null 。
public class PhantomReference<T> extends Reference<T> {
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
复制代码
虚引用还必须和 引用队列(ReferenceQueue)联合使用,当GC开始回收对象的时候,判断到该对象仅存在虚引用,就会在回收对象前,把这个虚引用加入到关联的引用队列中。因此,我们就可以在引用队列中查看某个引用是否在队列中存在,然后就可以得出该对象被垃圾回收了,发出一个系统通知。
总结:
引用类型 | 回收时间 | 用途 |
---|---|---|
强引用 | GC时不回收,可以把该对象置为null等回收 | 项目中广泛使用 |
软引用 | GC时内存不足时才回收 | 内存敏感的缓存中使用 |
弱引用 | GC时回收 | 对象缓存使用 |
虚引用 | GC时回收 | 配合引用队列使用,垃圾回收发送通知 |
参考资料:
- 《Java 核心技术 36 讲》