cglib版本爲cglib-nodep-2.2.jar.
本次只爲演示在使用中出現的Java內存泄漏的問題,以及如何解決這樣的問題。
cglib的應用是非常多的,但是當我們使用它的時候,如果一不小心,等出了問題再去查,就比較杯具了。所以最好的解決方案就是寫代碼時就注意這些細節。(當然了,不能指望在開發階段不引入Bug)
近期項目在做壓力測試,暴露了內存泄漏的Bug,cglib的使用不當便是原因之一。
下面來介紹代碼。
清單1:
- 1package com.jn.proxy;
- 2
- 3import java.lang.reflect.Method;
- 4
- 5import net.sf.cglib.proxy.Callback;
- 6import net.sf.cglib.proxy.CallbackFilter;
- 7import net.sf.cglib.proxy.Enhancer;
- 8import net.sf.cglib.proxy.MethodInterceptor;
- 9import net.sf.cglib.proxy.MethodProxy;
- 10import net.sf.cglib.proxy.NoOp;
- 11
- 12/** *//**
- 13 * 步驟方法攔截器.<br>
- 14 *
- 15 */
- 16public 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會將這些“垃圾”回收。