java reference

最近整理校招题目, 看到一个如何 利用 weak reference 和 referenceQueue 检测内存泄漏的题目, 顿时发现自己也不会。 于是查了下资料补习补习。

首先明确下 java 里, 对象被创建后,不管啥引用, 当对象没有人强引用时 并且 gc 发生时,对象会被清除(最终会被清除)。

然后,java 引用有

  • strong reference

  • SoftReference
  • WeakReference
  • PhantomReference
  • FinalReference

strong reference 没有具体的类, 因为 所有的java 对象默认就是 strong reference, 它的特点就是当没有人引用他时 会被gc

后4个 都有具体的类, 那么和 默认的 strong reference 有啥区别呢?

首先 weakReference 和 strongReference 一样, 没有强引用时, 才能被gc。

那么它存在的意义是什么?

网上查了下, 基本都是 weakhashmap 的例子,它可以帮你当 key 被清除时, 把对应的 value 也去除。 但是大多数情况下 我们不会这么使用, 因为他要手动代码 将key 置为null。 才能让 value 清除, 比较蛋疼。

后来网上查到个例子很有意义:

  • Activity具有自身的生命周期,Activity中新开启的线程运行过程中,可能此时用户按下了Back键,或系统内存不足等希望回收此Activity, 由于Activity中新起的线程并不会遵循Activity本身的什么周期,也就是说,当Activity执行了onDestroy,由于线程以及Handler 的HandleMessage的存在, 使得系统本希望进行此Activity内存回收不能实现,因为非静态内部类中隐性的持有对外部类的引用,导致可能存在的内存泄露问题。

  • 因此,在Activity中使用Handler时,一方面需要将其定义为静态内部类形式,这样可以使其与外部类(Activity)解耦,不再持有外部类的引用, 同时由于Handler中的handlerMessage一般都会多少需要访问或修改Activity的属性,此时,需要在Handler内部定义指向此Activity的WeakReference, 使其不会影响到Activity的内存回收同时,可以在正常情况下访问到Activity的属性。

那么 WeakReference 和 SoftReference 的区别?

  • 被 SoftReference 修饰的的对象只要内存足够,垃圾回收器就不会回收。 这也是为啥上面写 “最终” 会被清除。

SoftReference 可以用来实现缓存。

PhantomReference ?

  • 仅能用来跟踪对象是否被 gc 掉。 而且比 weakReference 更准确。

为啥更准确

weakReference 只要被标记为 null 了,然后 gc 后, jvm 知道后,就会 通知到 referenceHandler, 写入reference 对象到 referendceQueue (不管是否真的gc掉)

PhantomReference 必须要对象被 gc掉之后, referenceQueue 才会收到对象。

这里举一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Object obj1 = new Object();
ReferenceQueue<Object> referenceQueue1 = new ReferenceQueue<>();
PhantomReference<Object> phantomReference = new PhantomReference<>(obj1, referenceQueue1);

Object obj2 = new Object();
ReferenceQueue<Object> referenceQueue2 = new ReferenceQueue<>();
WeakReference<Object> weakReference = new WeakReference<Object>(obj2, referenceQueue2);

obj1 = null;
obj2 = null;
System.out.println(referenceQueue1.poll());
System.out.println(referenceQueue2.poll());
System.gc();
System.out.println(referenceQueue1.poll());
System.out.println(referenceQueue2.poll());
Thread.sleep(1000);
System.out.println(referenceQueue1.poll());
System.out.println(referenceQueue2.poll());
1
2
3
4
5
6
null
null
null
java.lang.ref.WeakReference@30a3107a
java.lang.ref.PhantomReference@33c7e1bb
null

当然多次跑下来会发现,还会发生以下结果

1
2
3
4
5
6
null
null
java.lang.ref.PhantomReference@30a3107a
java.lang.ref.WeakReference@33c7e1bb
null
null

猜测, 因为 如果 gc 的快, 则会发生后面的情况, gc 慢则会发生前面的情况。

最后是 FinalReference, 这个用于什么地方?

网上找到了 你假笨分析的文章

简单来说, 就是 jvm 会对那些实现了 finalize 方法的对象标记为 FinalReference.

然后利用和上面几个Reference 一样的 ReferenceQueue 的对象方式,接受通知,调用 finalize 方法。

至于什么时候 jvm 触发事件,是在 gc开始时 发现某个对象仅被 Finalizer 类引用,则就会发出通知,让 其执行 finalize 方法,(这时 对象还未被回收)

finalize 完成后会将 FinalReference 设置为 null, 让 gc 回收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void runFinalizer(JavaLangAccess jla) {
synchronized (this) {
if (hasBeenFinalized()) return;
remove();
}
try {
Object finalizee = this.get();
if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
jla.invokeFinalize(finalizee);

/* Clear stack slot containing this variable, to decrease
the chances of false retention with a conservative GC */

finalizee = null;
}
} catch (Throwable x) { }
super.clear();
}

FinalReference 其实相当于一个强引用, 确保执行 finalize 方法,所以不能立即回收掉, 但是呢, 执行 finalize 方法的线程有且只有一个, 而且优先级比较低,如果cpu 资源紧张的话,会导致 大量 finalize 对象挤压, 漫漫进入老年带。

也就是说, 尽量不要使用 finalize, 或者 即使使用了,也不要做一些非常耗时的操作, 避免对 gc 不友好。

了解过一遍后, 会过来看 检测垃圾回收的问题,

先看了网上的开源项目, 看看业界是怎么用的。

netty

netty 使用 PhantomReference 来检测内存泄漏,

原理就是 因为内部使用自己实现的 bytebufPool , 所以申请一个 ByteBuf 后, 如果不用了需要自己手动 release。

如果没有调用 release, 那么就可以认为是泄漏了。

然后 netty 定义了个 DefaultResourceLeak 的类, 他继承自 PhantomReference 让他附属在每一个 申请的 ByteBuf 对象上面。

并且申请时,记录下这个对象 存在 一个 map里面。

判断泄漏的逻辑就是, 如果调用了 release, 那么release 会把map里面的 对象清除。

如果没有调用, 那么 Bytebuf 被 gc 后(没调 release), 则会受到 referenceQueue 的通知, 看看 map 有没有, 有的话说明 泄漏了。

leakcanary

这个是android 的开源内存泄漏组件

看了个大概, 主要是用了 WeakReference , 自己实现了个叫 KeyedWeakReference 的类。

所有要检查的对象 注册到 RefWatcher 中, 里面又一个 Set 存 对象,

然后 她会暴力的 gc 下,然后查看 referenceQueue 有没有受到,没受到的话,说明还有人引这,说明泄漏了。

最后有个问题,她们选择 reference 的选型问题

  • leakcanary 能用 phantomReference 吗?

不行,因为 phantomReferece 要真正gc 掉之后, referenceQueue 才会收到通知,而异步 gc 的话并不保证 一定gc 完成。

  • 那么 netty 能不能把 PhantomReference 替换成 WeakReference ?

我觉得可以, 他并不在乎是否真正gc 掉,只要对不上 就是泄漏了。

http://hongjiang.info/java-referencequeue/

http://souly.cn/%E6%8A%80%E6%9C%AF%E5%8D%9A%E6%96%87/2015/10/13/java%E7%9A%84%E5%9B%9B%E7%A7%8D%E5%BC%95%E7%94%A8/

http://lovestblog.cn/blog/2015/07/09/final-reference/

avatar

lelouchcr's blog