Java有四种引用:强引用、软引用、弱引用和虚引用
整体结构
java.lang.ref包下
-
强引用:Reference
-
软引用:SoftReference
-
弱引用:WeakReference
-
虚引用:PhantomReference
-
引用队列:ReferenceQueue
强引用
当内存不足时,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收。
强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器就不会碰这种对象。在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,不可能被垃圾回收机制回收,即使该对象以后永远都不会被用到JVM也不会回收。因此强引用是造成Java内存泄漏的主原因之一。
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为null,就是可以被垃圾收集的了。
Student s = new Student
软引用
内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。
软引用作用是实现内存敏感的高速缓存。软引用可以和一个引用队列联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机会把软应用加入到与之关联的引用队列中。
package ref;
import java.lang.ref.SoftReference;
public class SoftReferenceDemo {
/**
* 内存够用就保留,不够就回收
*/
public static void softRef_Memory_Enough() {
Object o1 = new Object();
SoftReference<Object> softReference = new SoftReference<>(o1);
System.out.println(o1); //java.lang.Object@14ae5a5
System.out.println(softReference.get()); //java.lang.Object@14ae5a5
o1 = null;
System.gc();
System.out.println(o1); //null
System.out.println(softReference.get()); //java.lang.Object@14ae5a5
}
/**
* 生成大对象,设置小内存,自动GC
* -Xms5m -Xmx5m -XX:+PrintGCDetails
*/
public static void softRef_Memory_NotEnough() {
Object o1 = new Object();
SoftReference<Object> softReference = new SoftReference<>(o1);
System.out.println(o1);
System.out.println(softReference.get());
o1 = null;
try {
// 创建大对象
byte[] bytes = new byte[30 * 1024 * 1024];
} catch (Throwable e) {
e.printStackTrace();
} finally {
System.out.println(o1); //null
System.out.println(softReference.get()); //null
}
}
public static void main(String[] args) {
softRef_Memory_Enough();
softRef_Memory_NotEnough();
}
}
软引用队列案例
作为一个Java对象,SoftReference对象除了具有保存软引用的特殊性之外,也具有Java对象的一般性。所以,当软可及对象被回收之后,虽然这个SoftReference对象的get()方法返回null,但这个SoftReference对象已经不再具有存在的价值,需要一个适当的清除机制,避免大量SoftReference对象带来的内存泄漏。在java.lang.ref包里还提供了ReferenceQueue。如果在创建SoftReference对象的时候,使用了一个ReferenceQueue对象作为参数提供给SoftReference的构造方法,如:
ReferenceQueue queue = new ReferenceQueue();
SoftReference ref=new SoftReference(aMyObject, queue);
当这个SoftReference所软引用的aMyOhject被垃圾收集器回收的同时,ref所强引用的SoftReference对象被列入ReferenceQueue中。可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强引用对象被回收,再把这些失去所软引用的对象的SoftReference对象清除掉。
例如:
//继承SoftReference,实现对对象的软引用
//这个类所引用的目标对象会在JVM内存不足时自动回收
private class WeakEmployee extends WeakReference<Employee> {
private String key;
public String getKey() {
return key;
}
public WeakEmployee(Employee referent, ReferenceQueue<Employee> queue) {
super(referent, queue);
this.key = referent.getId();
}
}
private void cleanCache() {
WeakEmployee se = null;
while((se = (WeakEmployee)queue.poll()) != null) {
referent.remove(se.getKey());
System.out.println("对象ID : " + se.getKey() + "已经被JVM回收");
}
}
参考:https://www.cnblogs.com/daxin/archive/2013/06/03/3114679.html
弱引用
一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。很软引用类似,只是回收的更快。weak引用对象常常用于Map数据结构中,引用占用的内存空间较大的对象。
import java.lang.ref.WeakReference;
public class WeakReferenceDemo {
public static void main(String[] args) {
Object o1 = new Object();
WeakReference<Object> weakReference = new WeakReference<>(o1);
System.out.println(o1); //java.lang.Object@14ae5a5
System.out.println(weakReference.get()); //java.lang.Object@14ae5a5
o1 = null;
System.gc();
System.out.println(o1); //null
System.out.println(weakReference.get()); //null
}
}
应用场景
假如有一个应用需要读取大量的本地图片:
- 如果每次读取图片都从硬盘读取则会影响性能;
- 如果一次性全部加载到内存中又可能造成内存溢出。
此时可以使用软引用或弱引用解决。
设计思路为:用一个HashMap
来保存图片路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而避免OOM问题。
Map<String,SoftReference<Bitmap>> imageCache = new HashMap<>();
WeakHashMap
WeakHashMap的键是"弱键"。在WeakHashMap中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。
public class TestWeakHashMap {
public static void main(String[] args) {
WeakHashMap<String, String> weakHashMap = new WeakHashMap<>(10);
String key0 = new String("kuang");
String key1 = new String("zhong");
String key2 = new String("wen");
// 存放元素
weakHashMap.put(key0, "q1");
weakHashMap.put(key1, "q2");
weakHashMap.put(key2, "q3");
System.out.printf("weakHashMap: %s\n", weakHashMap);
// 是否包含某key
System.out.printf("contains key kuang : %s\n", weakHashMap.containsKey(key0));
System.out.printf("contains key zhong : %s\n", weakHashMap.containsKey(key1));
// 是否包含某value
System.out.printf("contains value 0 : %s\n", weakHashMap.containsValue(0));
// 移除key
weakHashMap.remove(key2);
System.out.printf("weakHashMap after remove: %s", weakHashMap);
// 这意味着"弱键"key0再没有被其它对象引用,调用gc时会回收WeakHashMap中与key0对应的键值对
key0 = null;
// 内存回收,这里会回收WeakHashMap中与"key0"对应的键值对
System.gc();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 遍历WeakHashMap
Iterator iter = weakHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry en = (Map.Entry) iter.next();
System.out.printf("next : %s - %s\n", en.getKey(), en.getValue());
}
// 打印WeakHashMap的实际大小
System.out.printf("after gc WeakHashMap size: %s\n", weakHashMap.size());
}
}
依次输出
weakHashMap: {wen=q3, zhong=q2, kuang=q1}
contains key kuang : true
contains key zhong : true
contains value 0 : false
weakHashMap after remove: {zhong=q2, kuang=q1}
next : zhong - q2
after gc WeakHashMap size: 1
参考:https://blog.csdn.net/u014294681/article/details/86522487
虚引用
虚引用不会决定对象的生命周期,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象。
当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
import java.lang.ref.ReferenceQueue;
import java.lang.ref.PhantomReference;
public class PhantomReferenceDemo {
public static void main(String[] args) throws InterruptedException {
Object o1 = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomReference = new PhantomReference<>(o1,referenceQueue);
System.out.println(o1);
System.out.println(phantomReference.get());
System.out.println(referenceQueue.poll());
o1 = null;
System.gc();
Thread.sleep(500);
System.out.println(o1);
System.out.println(phantomReference.get());
System.out.println(referenceQueue.poll());
}
}
输出
java.lang.Object@14ae5a5
null
null
null
null
java.lang.ref.PhantomReference@7f31245a