cglib版本為cglib-nodep-2.2.jar.
本次只為演示在使用中出現的Java內存泄漏的問題,以及如何解決這樣的問題。
cglib的應用是非常多的,但是當我們使用它的時候,如果一不小心,等出了問題再去查,就比較杯具了。所以最好的解決方案就是寫代碼時就注意這些細節。(當然了,不能指望在開發階段不引入Bug)
近期項目在做壓力測試,暴露了內存泄漏的Bug,cglib的使用不當便是原因之一。
下面來介紹代碼。
清單1:
1
package com.jn.proxy;
2
3
import java.lang.reflect.Method;
4
5
import net.sf.cglib.proxy.Callback;
6
import net.sf.cglib.proxy.CallbackFilter;
7
import net.sf.cglib.proxy.Enhancer;
8
import net.sf.cglib.proxy.MethodInterceptor;
9
import net.sf.cglib.proxy.MethodProxy;
10
import net.sf.cglib.proxy.NoOp;
11
12
/** *//**
13
* 步驟方法攔截器.<br>
14
*
15
*/
16
public class CglibLeak1
{
17
18
public <T> T newProxyInstance(Class<T> clazz)
{
19
return newProxyInstance(clazz, new MyInterceptor(), new MyFilter());
20
}
21
22
/** *//**
23
* 創建一個類動態代理.
24
*
25
* @param <T>
26
* @param superclass
27
* @param methodCb
28
* @param callbackFilter
29
* @return
30
*/
31
public static <T> T newProxyInstance(Class<T> superclass, Callback methodCb, CallbackFilter callbackFilter)
{
32
Enhancer enhancer = new Enhancer();
33
enhancer.setSuperclass(superclass);
34
enhancer.setCallbacks(new Callback[]
{ methodCb, NoOp.INSTANCE });
35
enhancer.setCallbackFilter(callbackFilter);
36
37
return (T) enhancer.create();
38
}
39
40
/** *//**
41
* 實現MethodInterceptor接口
42
*
43
* @author l
44
*
45
*/
46
class MyInterceptor implements MethodInterceptor
{
47
@Override
48
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
49
throws Throwable
{
50
return null;
51
}
52
}
53
54
/** *//**
55
* 實現CallbackFilter接口
56
*
57
* @author l
58
*/
59
class MyFilter implements CallbackFilter
{
60
@Override
61
public int accept(Method method)
{
62
// Do some thing
63
return 1;
64
}
65
}
66
67
/** *//**
68
* 測試代碼
69
* @param args
70
* @throws InterruptedException
71
*/
72
public static void main(String args[]) throws InterruptedException
{
73
CglibLeak1 leak = new CglibLeak1();
74
int count = 0;
75
while(true)
{
76
leak.newProxyInstance(Object.class); // 為了測試縮寫
77
Thread.sleep(100);
78
System.out.println(count++);
79
}
80
}
81
}
用JProfiler來觀察內存對象情況。
運行了一段時間(幾十秒鐘吧),內存對象的情況如圖所示:

我們看到 MyFilter 的Instance count 已經達到了1266個,而且隨著程序的繼續運行,Instance count還在不斷飆升,此情此景讓人心寒。
而且在JProfiler上點擊 Run GC 按鈕 這些對象并不會被回收。內存泄漏啦。
原因就是cglib自身的內部代理類緩存,將MyFilter對象加入到了緩存中,以至于該對象很大、并發量很大時,會造成內存溢出的Bug。
既然知道了原因,解決辦法就很明顯了。
1.重寫MyFilter類的equals和hashCode方法,這樣,當MyFilter對象準備進入緩存時,cglib會判斷是否為不同的MyFilter對象,如果是才加入到緩存。
我們重寫了equals和hashCode后,讓cglib認為這些MyFilter對象都是相同的。
2.將MyFilter類設置為靜態類。原理都是相同的。
我以第二種解決方案來修改代碼,請看。
清單2:
package com.jn.proxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;


/** *//**
* 步驟方法攔截器.<br>
*/

public class CglibLeak
{


private static MethodInterceptor myInterceptor = new MethodInterceptor()
{
@Override
public Object intercept(Object obj,
Method method, Object[] args,

MethodProxy proxy) throws Throwable
{
// do some things
return null;
}
};
// 創建實例
private static CallbackFilter myFilter = new MyFilter();


public static <T> T newProxyInstance(Class<T> clazz)
{
return newProxyInstance(clazz, myInterceptor, myFilter);
}


/** *//**
* 實現CallbackFilter接口
*
* @author l
*/

static class MyFilter implements CallbackFilter
{
@Override

public int accept(Method method)
{
// Do some thing
return 1;
}
}


/** *//**
* 創建一個類動態代理.
*
* @param <T>
* @param superclass
* @param methodCb
* @param callbackFilter
* @return
*/
public static <T> T newProxyInstance(Class<T> superclass, Callback methodCb,

CallbackFilter callbackFilter)
{
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(superclass);

enhancer.setCallbacks(new Callback[]
{methodCb, NoOp.INSTANCE});
enhancer.setCallbackFilter(callbackFilter);

return (T)enhancer.create();
}


/** *//**
* 測試代碼
*
* @param args
* @throws InterruptedException
*/

public static void main(String args[]) throws InterruptedException
{
int count = 0;

while (true)
{
newProxyInstance(Object.class); // 為了測試縮寫
Thread.sleep(100);
System.out.println(count++);
}
}
}

運行后的結果應該很明顯了:
MyFilter的Instance count 一直為1.
問題解決了。
因為我的MyFilter類中沒有成員變量,所以在多線程并發訪問時也不會出現問題。
如果以方案1 來解決這個內存泄漏問題情況是怎樣的呢?
清單3:
package com.jn.proxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;


/** *//**
* 步驟方法攔截器.<br>
*/

public class CglibLeak
{


private static MethodInterceptor myInterceptor = new MethodInterceptor()
{
@Override
public Object intercept(Object obj,
Method method, Object[] args,

MethodProxy proxy) throws Throwable
{
// do some things
return null;
}
};


public <T> T newProxyInstance(Class<T> clazz)
{
return newProxyInstance(clazz, myInterceptor, new MyFilter());
}


/** *//**
* 實現CallbackFilter接口
*
* @author l
*/

class MyFilter implements CallbackFilter
{
@Override

public int accept(Method method)
{
// Do some thing
return 1;
}

@Override

public boolean equals(Object o)
{

if (o instanceof MyFilter)
{
return true;
}
return false;
}

@Override

public int hashCode()
{
return 10011; // 沒什么原則,只為測試
}
}


/** *//**
* 創建一個類動態代理.
*
* @param <T>
* @param superclass
* @param methodCb
* @param callbackFilter
* @return
*/
public static <T> T newProxyInstance(Class<T> superclass, Callback methodCb,

CallbackFilter callbackFilter)
{
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(superclass);

enhancer.setCallbacks(new Callback[]
{methodCb, NoOp.INSTANCE});
enhancer.setCallbackFilter(callbackFilter);

return (T)enhancer.create();
}


/** *//**
* 測試代碼
*
* @param args
* @throws InterruptedException
*/

public static void main(String args[]) throws InterruptedException
{
CglibLeak l = new CglibLeak();
int count = 0;

while (true)
{
l.newProxyInstance(Object.class); // 為了測試縮寫
Thread.sleep(100);
System.out.println(count++);
}
}
}

運行一段時間后(幾十秒),JProfiler的觀測結果為:

MyFilter的對象還是很多,這是不是就表明 內存泄漏的問題依然存在呢。
當然不是,因為JVM垃圾回收策略的原因,我們new出來的MyFilter對象并不是 一旦成為垃圾就立即 被回收的。
經觀察,當Instance count 為600左右時,GC會將這些“垃圾”回收。
解決問題之際感慨一下:Java內存問題無處不在啊。
本文為原創,歡迎轉載,轉載請注明出處BlogJava。