Java中有四种引用类型依次为:
这四种引用强度依次逐渐减弱
Java 中引入四种引用的目的是让程序自己决定对象的生命周期
,JVM 是通过垃圾回收器GC
对这四种引用做不同的处理,来实现对象生命周期的改变。
JDK 8中的 UML关系图
FinalReference 类是包内可见,其他三种引用类型均为 public,可以在应用程序中直接使用。
Java设计这四种引用的主要目的有两个:
强引用是最普遍的一种引用,我们写的代码,99.9999%都是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。类似 Object obj = new Object()
这类的引用。只要某个对象有强引用与之关联,这个对象永远不会被回收,即使内存不足,JVM宁愿抛出OOM,也不会去回收。
当一个对象被强引用变量引用时,它处于可达状态
,是不可能被垃圾回收器回收的,即使该对象永远不会被用到也不会被回收。
Java垃圾回收器GC采用可达性分析法 来确认对象是否存活。如果一个对象没有任何对象所引用,这个对象属于不可达对象,在下一次GC会被回收。反之,当一个对象处于可达对象,该对象不会被GC所回收
当内存空间不足,Java虚拟机宁愿抛出OOM错误,使程序异常终止,也不会回收这种对象
。因此强引用有时也是造成Java 内存泄露
的原因之一。
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显示地将相应(强)引用赋值为 null
,一般认为就是可以被垃圾收集器回收。(具体回收时机还要要看垃圾收集策略)。
例如:
StringBuffer str = new StringBuffer("hello world");
局部变量str
会被放到栈
里,而StringBuffer实例对象
会被放在堆内
,局部变量str指向堆内的StringBuffer对象,通过str可以操作该对象,那么str就是StringBuffer的强引用
当执行这条语句
StringBuffer str1 = str;
强引用具备如下特点:
通过强引用可以直接访问目标对象
- 强引用所指向的对象在任何时候都不会被系统回收,虚拟机宁愿抛出OOM(内存溢出)异常,也不会回收强引用所指向的对象
- 强引用可能导致内存泄漏(站着空间不释放,积累的多了内存泄漏会导致内存溢出)
代码演示
@Test
public void testStrongReference() {
Object o1 = new Object();
Object o2 = o1;
//将强引用去除
o1 = null;
System.gc();
System.out.println(o1); //null
System.out.println(o2); //java.lang.Object@2503dbd3
}
执行结果
o1已经被回收,但是 o2 强引用 o1,一直存在,所以不会被GC回收
那么什么时候才可以被回收呢?
如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。
o1 = null;
o2 = null;
软引用是相对强引用弱化了一些的引用,Java1.2后用java.lang.ref.SoftReference
类来实现,可以让对象避免一些垃圾收集。
软引用就是把对象用
SoftReferenc
e包裹一下,当我们需要从软引用对象获得包裹的对象,只要get一下就可以了:
主要用来描述一些还有用,但并非必需的对象
。被软引用变量引用的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中并进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出异常。
对于只有软引用的对象来说:当系统内存充足时它不会被回收,当系统内存不足时它才会被回收。
import java.lang.ref.SoftReference;
//VM options: -Xms5m -Xmx10m
public class SoftRefenenceDemo {
public static void main(String[] args) {
softRefMemoryEnough();//内存够用时软引用的处理
System.out.println("------内存不够用的情况------");
softRefMemoryNotEnough();//内存不足时软引用的处理
}
/**
* 内存够用时软引用的处理
*/
private static void softRefMemoryEnough() {
Object o1 = new Object();
//使用软引用变量引用 o1
SoftReference<Object> s1 = new SoftReference<Object>(o1);
System.out.println(o1);
System.out.println(s1.get());
//将强引用去除
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(s1.get());
}
/**
* 内存不足时软引用的处理
*
* JVM配置`-Xms5m -Xmx10m` ,然后故意new一个一个大对象,使内存不足产生 OOM,看软引用回收情况
*/
private static void softRefMemoryNotEnough() {
Object o1 = new Object();
//使用软引用变量引用 o1
SoftReference<Object> s1 = new SoftReference<Object>(o1);
System.out.println(o1);
System.out.println(s1.get());
//将强引用去除
o1 = null;
try {
//创建10M对象
byte[] bytes = new byte[10 * 1024 * 1024];
}catch (OutOfMemoryError e) {
e.printStackTrace();
}
System.out.println(o1);
System.out.println(s1.get());
}
}
可以很清楚的看到当内存不足时,会触发JVM的GC,如果GC后,内存还是不足,就会把软引用的包裹的对象给干掉,如果干掉软引用的包裹的对象后内存还是不足就会抛出OOM, 也就是只有在内存不足,JVM才会回收该对象
。
此外,软引用还可以附带一个引用队列
ReferenceQueue
,当对象可达性发生改时(由可达变为不可达,被回收),此时软引用进入队列,通过这个引用队列,可以跟踪对象的回收情况
软引用到底有什么用呢?
弱引用也是用来描述非必需对象
的,它的强度比软引用更弱一些
,被弱引用变量引用的对象只能生存到下一次垃圾收集发生之前。
当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
Java1.2后用java.lang.ref.WeakReference
类来实现,它比软引用的生存期更短。
弱引用的使用和软引用类似,使用WeakReference包裹对象即可:
对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,都会回收该对象占用的内存。
import java.lang.ref.WeakReference;
public class WeakReferenceDemo {
public static void main(String[] args) {
Object o1 = new Object();
WeakReference<Object> w1 = new WeakReference<Object>(o1);
System.out.println(o1);
System.out.println(w1.get());
//将强引用去除
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(w1.get());
}
}
可以很清楚的看到明明内存还很充足,但是触发了GC,资源还是被回收了。弱引用在很多地方都有用到,比如ThreadLocal、WeakHashMap。
使用WeakHashMap
public class WeakHashMapDemo {
public static void main(String[] args) throws InterruptedException {
myHashMap();
System.out.println("-------------------");
myWeakHashMap();
}
/**
* 测试GC HashMap
*/
public static void myHashMap() {
HashMap<String, String> map = new HashMap<String, String>();
String key = new String("k1");
String value = "v1";
map.put(key, value);
System.out.println(map);
key = null;
System.gc();
System.out.println("GC——>HashMap");
System.out.println(map);
}
/**
* 测试GC 弱引用WeakHashMap
* @throws InterruptedException
*/
public static void myWeakHashMap() throws InterruptedException {
WeakHashMap<String, String> map = new WeakHashMap<String, String>();
//String key = "weak";
// 刚开始写成了上边的代码
//思考一下,写成上边那样会怎么样? 那可不是引用了
String key = new String("weak");
String value = "map";
map.put(key, value);
System.out.println(map);
//去掉强引用
key = null;
System.gc();
System.out.println("GC——>WeakHashMap");
Thread.sleep(1000);
System.out.println(map);
}
}
ReferenceQueue 是用来配合
引用工作的,没有ReferenceQueue 一样可以运行。
ReferenceQueue是专门用来存放引用的, 当软引用,弱引用,虚引用对应的那个对象被回收后的同时,该引用会自动加入到你所定义的ReferenceQueue中.
SoftReference、WeakReference、PhantomReference 都有一个可以传递 ReferenceQueue 的构造器。
创建引用的时候,可以指定关联的队列
,当 GC 释放对象内存的时候,会将引用加入到引用队列。
如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动,这相当于是一种通知机制。
当关联的引用队列中有数据的时候,意味着指向的堆内存中的对象被回收。通过这种方式,JVM 允许我们在对象被销毁后,做一些我们自己想做的事情。
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
public class TestReferenceQueue {
public static void main(String[] args) {
ReferenceQueue rq = new ReferenceQueue();
WeakReference wr = new WeakReference(new TestReferenceQueue(), rq);
System.out.println("弱引用对应的对象:" + wr.get() + ", 弱引用本身:" + wr);
System.out.println("队列中对象:" + rq.poll());
/**
* TestReferenceQueue中的对象只有一个引用 就是wr弱引用
* 因此直接调用gc就可以
*/
System.gc();
System.out.println("弱引用对应的对象:" + wr.get() + ", 弱引用本身:" + wr);
System.out.println("队列中对象:" + rq.poll());
}
}
//弱引用对应的对象:com.demo.reference.TestReferenceQueue@3e57cd70, 弱引用本身:java.lang.ref.WeakReference@9a7504c
//队列中对象:null
//弱引用对应的对象:null, 弱引用本身:java.lang.ref.WeakReference@9a7504c
//队列中对象:java.lang.ref.WeakReference@9a7504c
执行结果
在弱引用对应的对象被回收后该弱引用对象本身也进入到了ReferenceQueue
中,ReferenceQueue
清除失去了弱引用对象的弱引用本身, 软引用,虚引用也是如此.
虚引用也称为 “幽灵引用” 或者 “幻影引用” ,它是最弱
的一种引用关系。
虚引用,顾名思义,就是形同虚设,与其他几种引用都不太一样,一个对象被虚引用变量所引用的,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。
Java1.2后用java.lang.ref.PhantomReference
来实现。
如果一个对象仅持有虚引用,那么它就和没有任何引用一样
,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(RefenenceQueue)联合使用。
虚引用的特点之一:虚引用必须与ReferenceQueue一起使用,当GC准备回收一个对象,如果发现它还有虚引用,就会在回收之前,把这个虚引用加入到与之关联的ReferenceQueue中。
虚引用的主要作用是跟踪对象垃圾回收的状态
。仅仅是提供了一种确保对象被finalize
以后,做某些事情的机制。
PhantomReference
的 get
方法总是返回 null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入 finalization
阶段,可以被 GC 回收,用来实现比 finalization 机制更灵活的回收操作。
虚引用特点之儿:无法通过虚引用来获取对一个对象的真实引用
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class PhantomReferenceDemo {
public static void main(String[] args) throws InterruptedException {
//声明强用对象
Object o1 = new Object();
//声明引用对象
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
//虚引用中配置引用对象和引用队列
PhantomReference<Object> phantomReference = new PhantomReference<Object>(o1, referenceQueue);
System.out.println(o1);//null
System.out.println(referenceQueue.poll());//null
System.out.println(phantomReference.get());//null
o1 = null;
System.gc();
Thread.sleep(3000);
System.out.println(o1);//null
System.out.println(referenceQueue.poll()); //引用队列中 java.lang.ref.PhantomReference@6e2c634b
System.out.println(phantomReference.get()); // null
}
}
执行结果
当发生GC,虚引用就会被回收,并且会把回收的通知放到ReferenceQueue中。
虚引用有什么用呢?
Java 允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。(但不推荐在代码中使用)
引用类型 | 被回收时间 | 用途 | 生存时间 |
---|---|---|---|
强引用 | 从来不会 | 对象的一般状态 | JVM停止运行时 |
软引用 | 内存不足时 | 对象缓存 | 内存不足时 |
弱引用 | jvm垃圾回收时 | 对象缓存 | gc运行后 |
虚引用 | 未知 | 未知 | 未知 |
在实际程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多
,这是因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生
利用软引用和弱引用解决OOM问题:假如有一个应用需要读取大量的本地图片,如果每次读取图片都从硬盘读取,则会严重影响性能,但是如果全部加载到内存当中,又有可能造成内存溢出,此时使用软引用可以解决这个问题。
设计思路是:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。
Reference源码(JDK8)