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 * 步骤方法拦截器.
     
  14. 14 *   
  15. 15 */ 
  16. 16public class CglibLeak1 {  
  17. 17 
  18. 18    public  T newProxyInstance(Class clazz) {  
  19. 19        return newProxyInstance(clazz, new MyInterceptor(), new MyFilter());  
  20. 20    }  
  21. 21 
  22. 22    /** *//**  
  23. 23     * 创建一个类动态代理.  
  24. 24     *   
  25. 25     * @param   
  26. 26     * @param superclass  
  27. 27     * @param methodCb  
  28. 28     * @param callbackFilter  
  29. 29     * @return  
  30. 30     */ 
  31. 31    public static  T newProxyInstance(Class 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.  * 步骤方法拦截器.
     
  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 newProxyInstance(Class 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   
  50.      * @param superclass  
  51.      * @param methodCb  
  52.      * @param callbackFilter  
  53.      * @return  
  54.      */ 
  55.     public static  T newProxyInstance(Class 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.  * 步骤方法拦截器.
     
  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 newProxyInstance(Class 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   
  61.      * @param superclass  
  62.      * @param methodCb  
  63.      * @param callbackFilter  
  64.      * @return  
  65.      */ 
  66.     public static  T newProxyInstance(Class 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会将这些“垃圾”回收。

你可能感兴趣的:(cglib使用不慎引发的Java内存泄漏)