一、为什么netty需要内存泄露检测
由于netty的ByteBuf可能申请自直接内存,这一块是内存是不纳入GC的,如果不释放,会导致直接内存泄露。
二、虚引用
虚引用在实际的引用被释放之前,会将虚引用保存到引用队列中,
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
Object oc = new Object();
PhantomReference<Object> data = new PhantomReference<Object>(oc, referenceQueue);
oc = null;
System.gc();
System.out.println("队列中的引用:" + referenceQueue.poll());
System.out.println("虚引用中的对象:" + data.get());
运行结果:
队列中的引用:java.lang.ref.PhantomReference@70a31398
虚引用中的对象:null
利用虚引用的这个特点,可以进行内存泄露检测,检测原理是:
a、继承PhantomReference,在其中记录实际的被引用对象的状态,是否进行了释放等等;
b、在被引用对象被gc回收时,检测被引用对象,是否进行了释放,如果没有释放,打印内存泄露信息
详细可进一步参考这篇文章:http://blog.sina.com.cn/s/blog_4a4f9fb50100u908.html
三、实现原理简介
a、继承PhantomReference
/**
* 用于内存泄露检测的数据
*
* @author fengjing.yfj
* @version $Id: DetectorTest.java, v 0.1 2015年10月11日 下午4:21:47 fengjing.yfj Exp $
*/
private static class DetetorData extends PhantomReference<Object> {
/** 是否已经释放掉 */
private AtomicBoolean freed;
public DetetorData(Object referent, ReferenceQueue<? super Object> q) {
super(referent, q);
freed = new AtomicBoolean(false);
}
/**
* 如果还没有释放,则设置freezed=true,并且返回true;否则直接返回false
*
* @return
*/
public boolean close() {
if (freed.compareAndSet(false, true)) {
return true;
}
return false;
}
}
被引用对象在引用计数减为0,进行资源释放的,需要调用DetetorData.close()方法,更改资源状态;如果引用队列中的引用计数对象没有调用过这个方法,说明引用计数没有被正确的释放。
b、实现检测工具
/**
* 检测工具
*
* @author fengjing.yfj
* @version $Id: DetectorTest.java, v 0.1 2015年10月11日 下午4:34:40 fengjing.yfj Exp $
*/
public static class Detector<T> {
/** 虚引用的保存的队列,虚引用被GC前,会保存到该队列中 */
private ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
public DetetorData open(T object) {
//内存泄露检测,只能检测上一个对象是否发生了内存泄露
leakDetect();
//为新的对象创建虚引用
return new DetetorData(object, referenceQueue);
}
private void leakDetect() {
for (;;) {
DetetorData data = (DetetorData) referenceQueue.poll();
if (data == null) {
break;
}
//close成功,说明DetetorData没有关闭,也即引用计数没有到0
if (data.close()) {
System.out.println("内存泄露......");
}
}
}
}
c、自定义引用计数
private static class MyRefCount extends AbstractReferenceCounted {
/** 检测的数据 */
private DetetorData detetorData;
public MyRefCount(Detector<MyRefCount> detector) {
detetorData = detector.open(this);
}
@Override
public ReferenceCounted touch(Object hint) {
return this;
}
public DetetorData dump() {
System.out.println("This=" + this + " " + detetorData);
return detetorData;
}
/**
* 当引用计数变成0的时候,会调用内存泄露检测数据的关闭
* @see io.netty.util.AbstractReferenceCounted#deallocate()
*/
@Override
protected void deallocate() {
detetorData.close();
}
}
四、netty的实现
netty的内存泄露检测原理和三中的是一样的,只是netty的内存泄露检测加入的检测级别的概念,
public enum Level {
/**
* Disables resource leak detection.
*/
//取消内存泄露检测
DISABLED,
/**
* Enables simplistic sampling resource leak detection which reports there is a leak or not,
* at the cost of small overhead (default).
*/
//使用抽样检测的方式(抽样间隔为:samplingInterval),并且仅仅只是打印是否发生了内存泄露,
SIMPLE,
/**
* Enables advanced sampling resource leak detection which reports where the leaked object was accessed
* recently at the cost of high overhead.
*/
///使用抽样检测的方式(抽样间隔为:samplingInterval),并且打印哪里发生了内存泄露
ADVANCED,
/**
* Enables paranoid resource leak detection which reports where the leaked object was accessed recently,
* at the cost of the highest possible overhead (for testing purposes only).
*/
//对每一个对象都进行检测,并且打印内存泄露的地方,我就是这么任性,这么偏执。负载较高,适合测试模式
PARANOID
}
对于不同的级别,会有不同的处理。
netty的实现详见:io.netty.util.ResourceLeakDetector<T>和io.netty.util.ResourceLeakDetector.DefaultResourceLeak<T>类,只要理解了三中的原理,应该可以很好理解源代码的,这里就不在冗述~