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会将这些“垃圾”回收。
解决问题之际感慨一下:Java内存问题无处不在啊。
本文为原创,欢迎转载,转载请注明出处BlogJava。