目录
什么是引用
我们在java中经常会对类进行实例化
Person p = new Person
等号左边是对象的“引用”,存储在栈中。等号右边是实例化的对象,存储在堆中。
这种引用也被称为强引用。
引用的结构图
引用类分强软弱虚以及引用队列,它们都继承于Object类,结构图如下
强引用
在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。无论JVM的内存是否充足,进行垃圾回收时永远都不会回收强引用对象。正因为它永远都不会被GC回收,所以它会造成Java内存泄漏问题
当强引用被删除或被赋值为null时,就会被GC回收掉。
public class StrongReferenceDemo {
public static void main(String[] args) {
// 这样定义的默认就是强应用
Object obj1 = new Object();
// 使用第二个引用,指向刚刚创建的Object对象
Object obj2 = obj1;
// 置空
obj1 = null;
// 垃圾回收
System.gc();
System.out.println(obj1);
System.out.println(obj2);
}
}
输出结果可以看到,即使obj1被设置成null,调用gc回收时, obj2也还是没有被回收掉
null
java.lang.Object@14ae5a5
软引用
软引用需要用Java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集,对于软引用的对象来讲:
- 当系统内存充足时,它不会被回收
- 当系统内存不足时,它会被回收
软引用通常在对内存敏感的程序中,比如高速缓存就用到了软引用,内存够用的时候就保留,不够用就回收。
软引用的应用场景:
如果有一个应用需要读取大量本地图片,存硬盘中读取太慢,存内存中可能会造成内存溢出。这时我们可以把对象放在“软引用的HashMap”中,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,有效避免了OOM问题
Map<String, SoftReference<String>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
软引用使用举例
public class SoftReferenceDemo {
/**
* 内存够用的时候
*/
public static void softRefMemoryEnough() {
// 创建一个强应用
Object o1 = new Object();
// 创建一个软引用
SoftReference<Object> softReference = new SoftReference<>(o1);
System.out.println(o1);
System.out.println(softReference.get());
o1 = null;
// 手动GC
System.gc();
System.out.println(o1);
System.out.println(softReference.get());
}
/**
* JVM配置,故意产生大对象并配置小的内存,让它的内存不够用了导致OOM,看软引用的回收情况
* -Xms5m -Xmx5m -XX:+PrintGCDetails
*/
public static void softRefMemoryNoEnough() {
System.out.println("========================");
// 创建一个强应用
Object o1 = new Object();
// 创建一个软引用
SoftReference<Object> softReference = new SoftReference<>(o1);
System.out.println(o1);
System.out.println(softReference.get());
o1 = null;
// 模拟OOM自动GC
try {
// 创建30M的大对象
byte[] bytes = new byte[30 * 1024 * 1024];
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(o1);
System.out.println(softReference.get());
}
}
public static void main(String[] args) {
softRefMemoryEnough();
softRefMemoryNoEnough();
}
}
内存够用时强软引用都存在,内存不够用,当我们手动给强引用置为null,强引用才会被回收。而软引用当内存不够时,会自动被回收。
java.lang.Object@7f31245a
java.lang.Object@7f31245a
[GC (Allocation Failure) [PSYoungGen: 31K->160K(1536K)] 682K->811K(5632K), 0.0003603 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 160K->96K(1536K)] 811K->747K(5632K), 0.0006385 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 96K->0K(1536K)] [ParOldGen: 651K->646K(4096K)] 747K->646K(5632K), [Metaspace: 3488K->3488K(1056768K)], 0.0067976 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 646K->646K(5632K), 0.0004024 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 646K->627K(4096K)] 646K->627K(5632K), [Metaspace: 3488K->3488K(1056768K)], 0.0065506 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
null
null
弱引用
不管内存是否够,只要有GC操作就会进行回收
弱引用需要用 java.lang.ref.WeakReference
类来实现,它比软引用生存期更短。对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的空间。弱引用的底层是WeakHashMap。WeakHashMap和HashMap类似,只不过它的Key是使用了弱引用的,也就是说,当执行GC的时候,WeakHashMap中的key会进行回收。
public class WeakReferenceDemo {
public static void main(String[] args) {
Object o1 = new Object();
WeakReference<Object> weakReference = new WeakReference<>(o1);
System.out.println(o1);
System.out.println(weakReference.get());
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(weakReference.get());
}
}
我们可以看到,当内存足够时,手动调用了一下GC后,弱引用就被回收了
java.lang.Object@14ae5a5
java.lang.Object@14ae5a5
[GC (System.gc()) [PSYoungGen: 5246K->808K(76288K)] 5246K->816K(251392K), 0.0008236 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 808K->0K(76288K)] [ParOldGen: 8K->675K(175104K)] 816K->675K(251392K), [Metaspace: 3494K->3494K(1056768K)], 0.0035953 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
null
null
虚引用
虚引用又称为幽灵引用,需要java.lang.ref.PhantomReference
类来实现。如果一个对象持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列ReferenceQueue联合使用。虚引用在被垃圾回收前会在ReferenceQueue中保存一下,用来跟踪对象被垃圾回收的状态,应用会收到一个系统通知或后续添加进一步的处理。类似于Spring AOP里面的后置通知
PhantomReference的get方法总是返回null,因此无法访问对象的引用对象。
引用队列 ReferenceQueue
引用队列就相当于一个副本,你可以把强软弱虚引用手动放到引用队列中(虚引用使用时必须放到引用队列中)。 放到引用队列中的引用对象即使被垃圾回收后,也可以在引用队列中打印该对象的信息。