在jdk1.2之后,java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference) 四种,这四种引用强度依次逐渐减弱。
对象层次的引用:
一、强引用
强引用就是指在程序代码之中普遍存在的,类似“Object o = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象,即使发生OOM(OutOfMemoryError)。
强引用是最普遍的引用:Object o = new Object();
如果要中断强引用和某个对象的关联,将对象的引用显示地设置为null,这样GC就会在合适的时候回收对象:
o = null; // 帮助垃圾收集器回收此引用所对应的对象
ArrayList的clear()实现源代码:
/**
* Removes all of the elements from this list. The list will
* be empty after this call returns.
*/
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
@Test
public void testStrong() {
//通过引用链获取到对象,程序运行过程任意一个环节不会为null
List<Integer> intList = new ArrayList<>();
intList.add(new Integer("2"));
Map<String, List<Integer>> map = new HashMap<>();
map.put("aa", intList);
System.out.println(map.get("aa").get(0));
}
二、软引用
软引用用来描述一些还有用,但并非必需的对象。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好的用来解决OOM的问题,并且这个特性很适合用来实现缓存:如网页缓存、图片缓存等。在jdk1.2之后,提供了SoftReference类来实现软引用。
@Test
public void testSoft1(){
//创建一个软引用,包含一个对象
SoftReference<Integer> soft = new SoftReference<>(new Integer("23456"));
//软引用包含的对象可以通过get()获取到
Integer num = soft.get();
}
@Test
public void testSoft2(){
//创建一个软引用,包含一个对象
SoftReference<Integer> soft = new SoftReference<>(new Integer("23456"));
//提醒系统进行一个垃圾收集
System.gc();
System.out.println(soft.get() == null); // false
}
网页缓存实例:
package org.jgs1904.demo;
import java.lang.ref.SoftReference;
/**
* @ClassName BrowserData
* @Description 网页缓存模拟实现
* @Author RenYuWen
* @Since 2019/12/13 15:52
*/
public class BrowserData {
public static void main(String[] args) {
Browser prev = new Browser(); // 获取页面进行浏览
SoftReference sr = new SoftReference(prev); // 浏览完毕后重置为软引用
if(sr.get() != null){
prev = (Browser) sr.get(); // 如果还没有被回收器回收,直接获取
}else{
prev = new Browser(); // 由于内存吃紧,所以对软引用的对象进行回收
sr = new SoftReference(prev); // 重新构建
}
}
static class Browser{}
}
图片缓存实例:
package org.jgs1904.demo;
import java.lang.ref.SoftReference;
/**
* @ClassName Demo03
* @Description 软引用
* @Author RenYuWen
* @Since 2019/12/12 18:38
*/
public class ImageData {
private String path;
private SoftReference<byte[]> dataRef; // 软引用
public ImageData(String path){
this.path = path;
dataRef = new SoftReference<>(new byte[0]);
}
// 读取图片数据并将数据返回
private byte[] readImage(){
return new byte[1024 * 1024]; // 省略读取文件的操作
}
// 获取图片数据
public byte[] getData(){
byte[] dataArray = dataRef.get(); // 从引用中获取文件数据
if (dataArray == null || dataArray.length == 0){ // 如果为null
dataArray = readImage(); // 重新获取图片数据
dataRef = new SoftReference<>(dataArray); // 重新设置为软引用
}
return dataArray; // 如果不为null,直接返回,不需要再重新获取图片的数据
}
}
软引用可以和一个引用队列(ReferenceQueue)联合使用。如果软引用所引用的对象被垃圾回收器回收。java虚拟机就会把这个软引用加入到与之关联的引用队列中。
三、弱引用
弱引用也是用来描述非必需对象的。但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收到被弱引用关联的对象。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用的作用在于解决强引用所带来的对象之间在存活时间上的耦合关系。弱引用最常见的用处是在集合类中,尤其在哈希表中。
哈希表的接口允许使用任何Java对象作为键来使用。当一个键值对被放入到哈希表中之后,哈希表对象本身就有了对这些键和值对象的引用。如果这种引用是强引用的话,那么只要哈希表对象本身还存活,其中所包含的键和值对象是不会被回收的。如果某个存活时间很长的哈希表中包含的键值对很多,最终就有可能消耗掉JVM中全部的内存。对于这种情况的解决办法就是使用弱引用来引用这些对象,这样哈希表中的键和值对象都能被垃圾回收。
在jdk1.2之后,提供了WeakReference类来实现弱引用。
@Test
public void testWeak1(){
//创建一个弱引用,包含一个对象
WeakReference<Integer> weak = new WeakReference<>(new Integer("23456"));
//弱引用包含的对象可以通过get()获取到
Integer weakNum = weak.get();
}
@Test
public void testWeak2(){
//创建一个弱引用,包含一个对象
WeakReference<Integer> weak = new WeakReference<>(new Integer("23456"));
//提醒系统进行一次垃圾回收
System.gc();
System.out.println(weak.get() == null); // true
}
四、虚引用
虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是希望能在这个对象被收集器回收时收到一个系统通知。在jdk1.2之后,提供了PhantomReference类来实现虚引用。
"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解此引用所对应的对象是否将要被回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
@Test
public void testPhantom1(){
//创建一个虚引用,包含一个对象
ReferenceQueue<Integer> intRq = new ReferenceQueue<>();
PhantomReference<Integer> phantom = new PhantomReference<>(new Integer("23456"),intRq);
//弱引用包含的对象可以通过get()获取到
Integer phantomNum = phantom.get();
}
@Test
public void testPhantom2(){
//创建一个虚引用,包含一个对象
ReferenceQueue<Integer> intRq = new ReferenceQueue<>();
PhantomReference<Integer> phantom = new PhantomReference<>(new Integer("23456"),intRq);
Integer phantomNum = phantom.get();
System.out.println(phantomNum == null); // true 虚引用获取不到包装的对象!虚引用用以调度回收前的清理工作!
}
五、对比
引用类型 | GC回收时间 | 用途 | 生存时间 |
---|---|---|---|
强引用 | never | 对象的一般状态 | JVM停止运行时 |
软引用 | 内存不足时 | 对象缓存 | 内存不足时终止 |
弱引用 | GC工作时 | 对象缓存 | GC后终止 |
虚引用 | unknown | unknown | unknown |