java中的四种引用
主要了解,强、软、弱、虚四种引用在垃圾回收时候的不同表现。垃圾回收的时候,会调用对象的 finalize() 方法。finalize() 这个方法我们一般需要去重写它,也不应该去重写它,调用它。
强引用
普通的引用就是强引用。只要强引用指向一个对象,GC就永远不会回收这个对象。
public class T01_NormalReference {
public static void main(String[] args) throws IOException {
M m = new M();
m = null;//释放对象,再无指针指向对象
// 显示调用GC
System.gc(); //DisableExplicitGC
// 阻塞住当前线程,GC是泡在别的线程里的,如果main线程直接走完退出了,那么整个程序就退出了,那么GC不GC也就没有什么意义了。
System.in.read();// 无任何含义,只是为了阻塞住当前线程。
}
}
软引用(SoftReference)
如果一个软引用指向一个对象,只有在内存不够用的情况下,GC才会回收这个对象。也就是说即使手动调用GC,只要内存够用,就不会回收。
- 软引用非常适合缓存使用(面试说说,一般实际用Redis)
/**
* 软引用
* 软引用是用来描述一些还有用但并非必须的对象。
* 对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。
* 如果这次回收还没有足够的内存,才会抛出内存溢出异常。
* -Xmx20M
*/
public class T02_SoftReference {
public static void main(String[] args) {
// 声明一个软引用指向一个1024*1024*10大小字节数组
SoftReference<byte[]> m = new SoftReference<>(new byte[1024*1024*10]);
//m = null;
System.out.println(m.get());
System.gc();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(m.get());
//再分配一个数组,heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会把软引用干掉
byte[] b = new byte[1024*1024*15];
System.out.println(m.get());//get为空
}
}
**弱引用(WeakReference)面试重点
弱引用遭到gc就会回收。它的作用是什么呢?(重点!)
作用
如果一个弱引用和有另外一个强引用指向同一个对象,只要这个强引用消失掉,我这个弱引用就不应该再管这个对象了。用在什么地方,一般用在容器里(记住这个结论)。一个典型的应用就是 ThreadLocal。
import java.lang.ref.WeakReference;
public class T03_WeakReference {
public static void main(String[] args) {
WeakReference<M> m = new WeakReference<>(new M());
System.out.println(m.get());//有值
System.gc();
System.out.println(m.get());//空值
ThreadLocal<M> tl = new ThreadLocal<>();
tl.set(new M());
tl.remove();
}
}
作业
看一下WeakHashMap有什么作用。
- 它是一个弱指针版本的HashMap,它里面维护了一个Entry的数组 table,Entry继承自弱引用WeakReference的的键值对。通过对 key 进行hash的方式得到其在 table 中的偏移量index,从而通过 table[index] 的方式,检索出 key 对应的 Entry 键值对。
虚引用
虚引用就干一件事,管理堆外内存的。如果有面试官问你这个问题,还能把堆外内存给解释清楚,那可牛大了。虚引用,基本没用,不是给程序员用的,是给写JVM虚拟机的人用的。
创建虚引用 PhantomReference,需要传给他一个 ReferenceQueue队列QUEUE。一旦虚引用被回收,这个虚引用会被装到这个QUEUE里,然后让你接收到一个通知。什么时候你检测到一个QUEUE里头有一个虚引用存在了,那说明什么呢?说明这个虚引用被回收了。虚引用PhantomReference是一个特别虚的引用,垃圾回收的时候,二话不说上来就把它干掉,只要看见就给它干掉。
虚引用干这样一件事,当这个对象被干掉的时候,你会收到一个通知,通知你的方式是,往这个队列QUEUE里扔进一个值。 什么时候内存满了出发垃圾回收的时候,另外一个线程就不断地监测这个队列QUEUE里有没有值啊,有没有值啊?如果有的话就说迷你跟这个值被扔进去了。所以如果你想得到这个通知,你就不断的去检测,这个队列里面有没有值,如果有,就说明虚引用被回收了。
public class T04_PhantomReference {
private static final List<Object> LIST = new LinkedList<>();
private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>();
public static void main(String[] args) {
PhantomReference<M> phantomReference = new PhantomReference<>(new M(), QUEUE);
new Thread(() -> {
while (true) {
LIST.add(new byte[1024 * 1024]);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
System.out.println(phantomReference.get());
}
}).start();
new Thread(() -> {
while (true) {
Reference<? extends M> poll = QUEUE.poll();
if (poll != null) {
System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll);
}
}
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 虚引用,你往里面get值,是get不到的。那它连一个值也拿不到,到底用来干嘛的?再说一遍,只是为了给你一个通知 。
- 写JVM的人,拿它来怎么用呢?有这么一种情况。NIO里面有一个比较新的buffer叫DirectBuffer,这个缓存不被JVM管理,而是直接被操作系统控制,又叫做堆外内存,这个buffer是指向堆外内存的,如果这个buffer设为空,垃圾回收器会自动回收它所对应的堆外内存吗,不会回收,也没法回收。那么写JVM的人怎么回收它呢?当检测到虚引用被这个垃圾回收器回收的时候,代码手动去回收堆外内存,通过c/C++的delete或者free函数。
- 现在Java里也提供了一个可以分配/手动回收堆外内存的方式,就是通过Unsafe。Unsafe这个类在JUC里头很多操作都用到了它,它里头有很多cas操作。