Java学习-引用总结
0x01 摘要
本篇文章主要是总结下java中的各种引用即:强引用、软引用、弱引用、虚引用。
0x02 强引用
引用是JAVA中默认采用的一种方式,我们平时创建的引用都属于强引用。
如果一个对象没有强引用,那么对象就可能会被回收。
使用强引用一定要注意避免内存泄露。
测试代码如下:
public void strongReferenceTest(){
Object obj = new Object();
Object objRef = obj;
obj = null;
System.gc();
System.out.println(obj);
}
0x03 软引用
如果一个对象只具有软引用那这个对象有以下特点:
- 内存空间足够时,垃圾回收器不会回收它。也就是说 即使发生了gc 但是还有大量内存可用,那也不会立刻回收软引用
- 如果内存空间不足,就会回收这些对象的内存。
- 只要垃圾回收器没有回收它,该对象就可以被程序使用。
- 软引用可用来实现内存敏感的高速缓存。
示例如下:
public void softReferenceTest(){
String str=new String("abc"); // 强引用
SoftReference<String> softRef=new SoftReference<String>(str); // 软引用
str = null;
// 当内存不足时,等价于:
/* If(JVM.内存不足()) {
str = null; // 转换为软引用
System.gc(); // 垃圾回收器进行回收
}*/
}
0x04 弱引用
4.1 弱引用概念
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。
当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,此时最佳方式就是弱引用。
在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联 的引用队列中。
4.2 弱引用示例
示例代码如下:
public void weakReferenceTest(){
// str强引用
String str = new String("hello");
// 引用队列
ReferenceQueue<String> rq = new ReferenceQueue<String>();
// 弱引用,关联了str rq队列
WeakReference<String> wf = new WeakReference<String>(str, rq);
// 取消"hello"对象的强引用
str=null;
// 强制gc
// 注意,实际场景中要慎用 System.gc(); 会导致FUll gc即整个heap gc
System.gc();
// 假如"hello"对象没有被回收,str1会强引用"hello"对象
String str1=wf.get();
System.out.println("str1 = " + str1);
// 假如"hello"对象没有被回收,rq.poll()返回null
Reference<? extends String> ref=rq.poll();
System.out.println("ref = " + ref);
}
4.3 WeakHashMap
WeakHashMap
是一个特殊的map,有着弱引用的key。也就是说,当某个key没有再被正常使用时,WeakHashMap中的这个key对应的entry
会被自动移除。更准确的说,一个拥有一个指定的key的mapping映射存在也不能阻止该key被GC回收。当一个key被GC回收掉后,他的entry会从该Map中被高效移除掉。这一点是WeakHashMap与其他map最大的不同之处。
当使用 WeakHashMap 时,即使没有显示的添加或删除任何元素,也可能发生如下情况:
- 调用两次size()方法返回不同的值;
- 两次调用isEmpty()方法,第一次返回false,第二次返回true;
- 两次调用containsKey()方法,第一次返回true,第二次返回false,尽管两次使用的是同一个key;
- 两次调用get()方法,第一次返回一个value,第二次返回null,尽管两次使用的是同一个对象。
WeakHashMap是软引用的一个典型的应用,更多信息请点击Java学习-容器-WeakHashMap
0x05 虚引用
虚引用形同虚设,它所引用的对象随时可能被垃圾回收。
虚引用的使用场景很窄,在JDK中,目前只知道在申请堆外内存时有它的身影。申请堆外内存时,在JVM堆中会创建一个对应的Cleaner对象,这个Cleaner类继承了PhantomReference,当DirectByteBuffer对象被回收时,可以执行对应的Cleaner对象的clean方法,做一些后续工作,这里是释放之前申请的堆外内存。
由于虚引用的get方法无法拿到真实对象,所以当你不想让真实对象被访问时,可以选择使用虚引用,
它唯一能做的是在对象被GC时,收到通知,并执行一些后续工作。
看到有些文章说虚引用可以清理已经执行finalize方法,但是还没被回收的对象,这简直就是误导人嘛,与finalize方法有关的引用是FinalReference,这个引用就是之前说的其它两种中的一个。
0x06 综合例子
在以下例程的References类中,依次创建了10个软引用、10个弱引用和10个虚引用,它们各自引用一个Grocery对象。
import java.lang.ref.*;
import java.util.*;
class Grocery {
private static final int SIZE = 10000;
// 属性d使得每个Grocery对象占用较多内存,有80K左右
private double[] d = new double[SIZE];
private String id;
public Grocery(String id) {
this.id = id;
}
public String toString() {
return id;
}
// 通过finalize方法观察该对象被gc认为可以回收
public void finalize() {
System.out.println("Finalizing " + id);
}
}
public class References {
private static ReferenceQueue<Grocery> rq = new ReferenceQueue<Grocery>();
public static void checkQueue() {
// 从队列中取出一个引用
Reference<? extends Grocery> inq = rq.poll();
if (inq != null)
System.out.println("In queue: " + inq + " : " + inq.get());
}
public static void main(String[] args) {
final int size = 10;
// 创建10个Grocery对象以及10个软引用
Set<SoftReference<Grocery>> sa = new HashSet<SoftReference<Grocery>>();
for (int i = 0; i < size; i++) {
SoftReference<Grocery> ref = new SoftReference<Grocery>(
new Grocery("Soft " + i), rq);
System.out.println("Just created: " + ref.get());
sa.add(ref);
}
System.gc();
checkQueue();
// 创建10个Grocery对象以及10个弱引用
Set<WeakReference<Grocery>> wa = new HashSet<WeakReference<Grocery>>();
for (int i = 0; i < size; i++) {
WeakReference<Grocery> ref = new WeakReference<Grocery>(
new Grocery("Weak " + i), rq);
System.out.println("Just created: " + ref.get());
wa.add(ref);
}
System.gc();
checkQueue();
// 创建10个Grocery对象以及10个虚引用
Set<PhantomReference<Grocery>> pa = new HashSet<PhantomReference<Grocery>>();
for (int i = 0; i < size; i++) {
PhantomReference<Grocery> ref = new PhantomReference<Grocery>(
new Grocery("Phantom " + i), rq);
System.out.println("Just created: " + ref.get());
pa.add(ref);
}
System.gc();
checkQueue();
}
}
从程序运行时的打印结果可以得出以下结论:
- 虚引用形同虚设,它所引用的对象随时可能被垃圾回收
- 具有弱引用的对象拥有稍微长的生命周期,当垃圾回收器执行回收操作时,有可能被垃圾回收
- 具有软引用的对象拥有较长的生命周期,但在Java虚拟机认为内存不足的情况下,也会被垃圾回收