cglib使用不慎引發的Java內存泄漏

cglib版本爲cglib-nodep-2.2.jar.
本次只爲演示在使用中出現的Java內存泄漏的問題,以及如何解決這樣的問題。
cglib的應用是非常多的,但是當我們使用它的時候,如果一不小心,等出了問題再去查,就比較杯具了。所以最好的解決方案就是寫代碼時就注意這些細節。(當然了,不能指望在開發階段不引入Bug)
近期項目在做壓力測試,暴露了內存泄漏的Bug,cglib的使用不當便是原因之一。
下面來介紹代碼。
清單1:
 

  1.  1package com.jn.proxy;  
  2.  2 
  3.  3import java.lang.reflect.Method;  
  4.  4 
  5.  5import net.sf.cglib.proxy.Callback;  
  6.  6import net.sf.cglib.proxy.CallbackFilter;  
  7.  7import net.sf.cglib.proxy.Enhancer;  
  8.  8import net.sf.cglib.proxy.MethodInterceptor;  
  9.  9import net.sf.cglib.proxy.MethodProxy;  
  10. 10import net.sf.cglib.proxy.NoOp;  
  11. 11 
  12. 12/** *//**  
  13. 13 * 步驟方法攔截器.<br>  
  14. 14 *   
  15. 15 */ 
  16. 16public class CglibLeak1 {  
  17. 17 
  18. 18    public <T> T newProxyInstance(Class<T> clazz) {  
  19. 19        return newProxyInstance(clazz, new MyInterceptor(), new MyFilter());  
  20. 20    }  
  21. 21 
  22. 22    /** *//**  
  23. 23     * 創建一個類動態代理.  
  24. 24     *   
  25. 25     * @param <T>  
  26. 26     * @param superclass  
  27. 27     * @param methodCb  
  28. 28     * @param callbackFilter  
  29. 29     * @return  
  30. 30     */ 
  31. 31    public static <T> T newProxyInstance(Class<T> superclass, Callback methodCb, CallbackFilter callbackFilter) {  
  32. 32        Enhancer enhancer = new Enhancer();  
  33. 33        enhancer.setSuperclass(superclass);  
  34. 34        enhancer.setCallbacks(new Callback[] { methodCb, NoOp.INSTANCE });  
  35. 35        enhancer.setCallbackFilter(callbackFilter);  
  36. 36 
  37. 37        return (T) enhancer.create();  
  38. 38    }  
  39. 39      
  40. 40    /** *//**  
  41. 41     * 實現MethodInterceptor接口  
  42. 42     *   
  43. 43     * @author l  
  44. 44     *  
  45. 45     */ 
  46. 46    class MyInterceptor implements MethodInterceptor {  
  47. 47        @Override 
  48. 48        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)  
  49. 49            throws Throwable {  
  50. 50            return null;  
  51. 51        }  
  52. 52    }  
  53. 53      
  54. 54    /** *//**  
  55. 55     * 實現CallbackFilter接口  
  56. 56     *   
  57. 57     * @author l  
  58. 58     */ 
  59. 59    class MyFilter implements CallbackFilter {  
  60. 60        @Override 
  61. 61        public int accept(Method method) {  
  62. 62            // Do some thing  
  63. 63            return 1;  
  64. 64        }  
  65. 65    }  
  66. 66      
  67. 67    /** *//**  
  68. 68     * 測試代碼  
  69. 69     * @param args  
  70. 70     * @throws InterruptedException  
  71. 71     */ 
  72. 72    public static void main(String args[]) throws InterruptedException {  
  73. 73        CglibLeak1 leak = new CglibLeak1();  
  74. 74        int count = 0;  
  75. 75        while(true) {  
  76. 76            leak.newProxyInstance(Object.class); // 爲了測試縮寫  
  77. 77            Thread.sleep(100);  
  78. 78            System.out.println(count++);  
  79. 79        }  
  80. 80    }  
  81. 81


用JProfiler來觀察內存對象情況。
運行了一段時間(幾十秒鐘吧),內存對象的情況如圖所示:
cglib使用不慎引發的Java內存泄漏_www.fengfly.com
我們看到 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:
 

  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. public class CglibLeak {  
  16.  
  17.     private static MethodInterceptor myInterceptor = new MethodInterceptor() {  
  18.                                                        @Override 
  19.                                                        public Object intercept(Object obj,  
  20.                                                            Method method, Object[] args,  
  21.                                                            MethodProxy proxy) throws Throwable {  
  22.                                                            // do some things  
  23.                                                            return null;  
  24.                                                        }  
  25.                                                    };  
  26.                                                    // 創建實例  
  27.     private static CallbackFilter    myFilter      = new MyFilter();  
  28.  
  29.     public static <T> T newProxyInstance(Class<T> clazz) {  
  30.         return newProxyInstance(clazz, myInterceptor, myFilter);  
  31.     }  
  32.  
  33.     /** *//**  
  34.      * 實現CallbackFilter接口  
  35.      *   
  36.      * @author l  
  37.      */ 
  38.     static class MyFilter implements CallbackFilter {  
  39.         @Override 
  40.         public int accept(Method method) {  
  41.             // Do some thing  
  42.             return 1;  
  43.         }  
  44.     }  
  45.  
  46.     /** *//**  
  47.      * 創建一個類動態代理.  
  48.      *   
  49.      * @param <T>  
  50.      * @param superclass  
  51.      * @param methodCb  
  52.      * @param callbackFilter  
  53.      * @return  
  54.      */ 
  55.     public static <T> T newProxyInstance(Class<T> superclass, Callback methodCb,  
  56.         CallbackFilter callbackFilter) {  
  57.         Enhancer enhancer = new Enhancer();  
  58.         enhancer.setSuperclass(superclass);  
  59.         enhancer.setCallbacks(new Callback[]{methodCb, NoOp.INSTANCE});  
  60.         enhancer.setCallbackFilter(callbackFilter);  
  61.  
  62.         return (T)enhancer.create();  
  63.     }  
  64.  
  65.     /** *//**  
  66.      * 測試代碼  
  67.      *   
  68.      * @param args  
  69.      * @throws InterruptedException  
  70.      */ 
  71.     public static void main(String args[]) throws InterruptedException {  
  72.         int count = 0;  
  73.         while (true) {  
  74.             newProxyInstance(Object.class); // 爲了測試縮寫  
  75.             Thread.sleep(100);  
  76.             System.out.println(count++);  
  77.         }  
  78.     }  
  79. }  


運行後的結果應該很明顯了:
cglib使用不慎引發的Java內存泄漏_www.fengfly.com

MyFilter的Instance count 一直爲1.
問題解決了。

因爲我的MyFilter類中沒有成員變量,所以在多線程併發訪問時也不會出現問題。

如果以方案1 來解決這個內存泄漏問題情況是怎樣的呢?
清單3:
 

  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. public class CglibLeak {  
  16.  
  17.     private static MethodInterceptor myInterceptor = new MethodInterceptor() {  
  18.                                                        @Override 
  19.                                                        public Object intercept(Object obj,  
  20.                                                            Method method, Object[] args,  
  21.                                                            MethodProxy proxy) throws Throwable {  
  22.                                                            // do some things  
  23.                                                            return null;  
  24.                                                        }  
  25.                                                    };  
  26.  
  27.     public <T> T newProxyInstance(Class<T> clazz) {  
  28.         return newProxyInstance(clazz, myInterceptor, new MyFilter());  
  29.     }  
  30.  
  31.     /** *//**  
  32.      * 實現CallbackFilter接口  
  33.      *   
  34.      * @author l  
  35.      */ 
  36.     class MyFilter implements CallbackFilter {  
  37.         @Override 
  38.         public int accept(Method method) {  
  39.             // Do some thing  
  40.             return 1;  
  41.         }  
  42.  
  43.         @Override 
  44.         public boolean equals(Object o) {  
  45.             if (o instanceof MyFilter) {  
  46.                 return true;  
  47.             }  
  48.             return false;  
  49.         }  
  50.  
  51.         @Override 
  52.         public int hashCode() {  
  53.             return 10011// 沒什麼原則,只爲測試  
  54.         }  
  55.     }  
  56.  
  57.     /** *//**  
  58.      * 創建一個類動態代理.  
  59.      *   
  60.      * @param <T>  
  61.      * @param superclass  
  62.      * @param methodCb  
  63.      * @param callbackFilter  
  64.      * @return  
  65.      */ 
  66.     public static <T> T newProxyInstance(Class<T> superclass, Callback methodCb,  
  67.         CallbackFilter callbackFilter) {  
  68.         Enhancer enhancer = new Enhancer();  
  69.         enhancer.setSuperclass(superclass);  
  70.         enhancer.setCallbacks(new Callback[]{methodCb, NoOp.INSTANCE});  
  71.         enhancer.setCallbackFilter(callbackFilter);  
  72.  
  73.         return (T)enhancer.create();  
  74.     }  
  75.  
  76.     /** *//**  
  77.      * 測試代碼  
  78.      *   
  79.      * @param args  
  80.      * @throws InterruptedException  
  81.      */ 
  82.     public static void main(String args[]) throws InterruptedException {  
  83.         CglibLeak l = new CglibLeak();  
  84.         int count = 0;  
  85.         while (true) {  
  86.             l.newProxyInstance(Object.class); // 爲了測試縮寫  
  87.             Thread.sleep(100);  
  88.             System.out.println(count++);  
  89.         }  
  90.     }  
  91. }  


運行一段時間後(幾十秒),JProfiler的觀測結果爲:
cglib使用不慎引發的Java內存泄漏_www.fengfly.com
MyFilter的對象還是很多,這是不是就表明 內存泄漏的問題依然存在呢。
當然不是,因爲JVM垃圾回收策略的原因,我們new出來的MyFilter對象並不是 一旦成爲垃圾就立即 被回收的。
經觀察,當Instance count 爲600左右時,GC會將這些“垃圾”回收。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章