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