Java中4种引用的级别由高到低依次为:
强引用 > 软引用 > 弱引用 > 虚引用
[Java] 纯文本查看 复制代码 class M {
@Override
protected void finalize() throws Throwable {
System.out.println("finalize");
}
}
强引用:
(只有指定m = null;时才进行回收)
[Java] 纯文本查看 复制代码 // 下列代码就是一个强引用
M m = new M();
软引用
(作用:非常适合缓存使用)
[Java] 纯文本查看 复制代码 // 运行下面代码的时候需要指定命令行参数(堆最大值20M):-Xmx20M
// 下面的代码会分配一个10M的空间,然后前两次get()都可以正常获取到
// 然后实例化一个强引用的15M的数组,这个时候就会内存泄漏
SoftReference<byte[]> m = new SoftReference<>(new byte[1024*1024*10]);
System.out.println(m.get());
System.gc();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(m.get());
// 再分配一个数组,heap将装不下,这时候系统会垃圾回收(启动的时候java会占用一部分内存),先回收一次,如果不够,会把软引用干掉
byte[] b = new byte[1024*1024*10];
System.out.println(m.get());
弱引用
(结论:只要垃圾回收器看到了就直接回收,与软引用区别:软引用是内存不够了才进行回收)
[Java] 纯文本查看 复制代码 WeakReference<M> m = new WeakReference<>(new M());
// M对象
System.out.println(m.get());
System.gc();
// null,调用了finalize()方法
System.out.println(m.get());
虚引用
(作用:在NIO中,就运用了虚引用管理堆外内存。)
(特点:无法通过虚引用来获取对一个对象的真实引用。)
(要求:必须和一个队列结合使用,除了写类库的人,一般没人用)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。你声明虚引用的时候是要传入一个queue的。当你的虚引用所引用的对象已经执行完finalize函数的时候,就会把对象加到queue里面。你可以通过判断queue里面是不是有对象来判断你的对象是不是要被回收了【这是重点,让你知道你的对象什么时候会被回收。因为对普通的对象,gc要回收它的,你是知道它什么时候会被回收】。
[Java] 纯文本查看 复制代码 // 运行下面代码的时候需要指定命令行参数(堆最大值20M):-Xmx20M
// 虚引用必须与ReferenceQueue一起使用,当GC准备回收一个对象,如果发现它还有虚引用,就会在回收之前,把这个虚引用加入到与之关联的ReferenceQueue中。
ReferenceQueue<M> queue = new ReferenceQueue<>();
List<byte[]> bytes = new ArrayList<>();
// 虚引用
PhantomReference<M> reference = new PhantomReference<>(new M(),queue);
new Thread(() -> {
for (int i = 0; i < 100; i++) {
bytes.add(new byte[1024 * 1024]);
}
}).start();
new Thread(() -> {
while (true) {
Reference<? extends M> poll = queue.poll();
if (poll != null) {
System.out.println("虚引用被回收了:" + poll);
}
}
}).start();
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
简单分析一下上述代码:
第一个线程往集合里面塞数据,随着数据越来越多,肯定会发生GC。
第二个线程死循环,从queue里面拿数据,如果拿出来的数据不是null,就打印出来。
从运行结果可以看到:当发生GC,虚引用就会被回收,并且会把回收的通知放到ReferenceQueue中。
|