java中的引用

java中的引用

强引用(NormalReference)

我们平时用的基本上都是强引用M m = new M();

只要强引用在,gc不回收该对象

1
2
3
4
5
6
7
8
9
//没事不要重写finalize方法,这里是为了演示gc时机
public class M {

@Override
protected void finalize() throws Throwable {
System.out.println("finalize方法被调用");
super.finalize();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class 强引用 {

public static void main(String[] args) throws IOException {
M m = new M();

System.gc();
System.out.println(m); //存在强引用,gc不会回收

m = null;
System.gc();
System.out.println(m); //引用没了,gc回收

System.in.read(); //阻塞main线程,垃圾回收线程需要时间
}
}

打印结果如下

引用.M@14ae5a5
finalize方法被调用
null


软引用(SoftReference)

软引用对象会在堆内存不足时回收

适合做缓存

在开始实验之前,先将右上角run按钮左边的本类名的v按钮选择Edit Configurations在vm options添加-Xmx20M,表示将堆最大值设置为20M

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 软引用对象会在堆内存不足时回收
* 适合做缓存
*/
public class 软引用 {

public static void main(String[] args) {
SoftReference<byte[]> m = new SoftReference<>(new byte[1024 * 1024 * 10]); //定义一个占10M的数组
System.out.println(m.get()); //有值
System.gc();
try {
Thread.sleep(1000); //睡1s保证gc完
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(m.get()); //因为对内存充足,发现并没有被gc回收
byte[] b = new byte[1024 * 1024 * 11]; //定义一个11M的数组,由于此时10+11大于20M,jvm自动gc回收
System.out.println(m.get()); //被回收,为null
}
}

打印结果为

[B@14ae5a5
[B@14ae5a5
null

如果b的内存太大,堆内存装不下,那么会报OutOfMemoryError: Java heap space异常


弱引用(WeakReference)

弱引用是遇到gc就回收

1
2
3
4
5
6
7
8
9
10
public class 弱引用 {

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()); //不管内存是否充足,遇到gc就回收
}
}

ThreadLocal中的Entry

弱引用很经典的一个例子就是ThreadLocal中的Entry,在之前的博客中有分析ThreadLocal为什么是弱引用,这里再次复习一下

当我们ThreadLocal tl = new ThreadLocal();的时候,实际上有两个引用指向了ThreadLocal,一个是tl本身是一个强引用,一个是getMap(当前线程)得到的ThreadLocalMap,它里面的Entry的key是一个弱引用,指向了ThreadLocal。

为什么这里要使用弱引用呢?

如果是强引用,则tl和key都是强引用,我们一般都是操作tl,也就是tl = null,但是key依然存在强引用,于是导致无法被gc回收,存在内存泄漏问题。

使用弱引用就不会内存泄漏?

不是的,即使key是弱引用,但是如果key被回收变为null,value无法被访问,依然存在内存泄漏问题。解决的办法是主动调用tl.remove();


虚引用(PhantomReference)

一般没人用,只有写类库的人会用

无论是否被回收,都get不到值

1
2
3
4
5
6
7
8
public class 虚引用 {
private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>(); //回收队列

public static void main(String[] args) {
PhantomReference<M> m = new PhantomReference<>(new M(), QUEUE);
System.out.println(m.get()); //null
}
}

作用是回收前有个通知,相当于一个钩子函数,堆内存不足的时候并没有直接回收,而是放进QUEUE回收队列里,gc会不断判断队列里是否有值,有则poll,这就相当于一个通知,gc如果想,可以对poll出来的这个虚引用做一些特殊处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class 虚引用 {
private static final List<Object> LIST = new LinkedList<>();
private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>(); //回收队列

public static void main(String[] args) {
PhantomReference<M> m = new PhantomReference<>(new M(), QUEUE);
System.out.println(m.get()); //null

new Thread(()->{
while (true){
try {
LIST.add(new byte[1024 * 1024 * 2]);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
System.out.println(m.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();
}
}
}

实际应用中,虚引用常被用于管理直接内存(堆外内存)

当然,堆外内存不一定是硬件,只是举个例子,硬件通过驱动程序加载到内存中才能供操作系统调用,以前JVM无法直接控制堆外内存, 需要拷贝到JVM的堆中才能控制,这种拷贝需要系统调用,涉及用户态和内核态的转换

零拷贝技术

java的nio提出了zero-copy零拷贝技术,可以管理堆外内存

1
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

这个allocateDirect方法源码如下,可以看到是DirectByteBuffer,这个DirectByteBuffer什么时候回收的呢?我们可以看到这个类下有个Cleaner,就这个名字也才得到是用来负责回收对象的

1
2
3
4
5
6
7
8
9
10
11
12
13
class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer{
...

private final Cleaner cleaner;

public Cleaner cleaner() { return cleaner; }

public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}

...
}

继续跟进Cleaner类

1
2
3
public class Cleaner extends PhantomReference<Object> {
private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue();
...

看源码讲究点到即止,是不是跟咱们自己写的 模拟简易垃圾回收线程 一模一样 有点相似?

也就是说,这个cleaner就是通过虚引用对堆外内存进行跟踪。


参考

bilibili马士兵说弱引用

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×