cglib使用不慎引发的Java内存泄漏

本次只为演示在使用中出现的Java内存泄漏的问题,以及如何解决这样的问题,cglib版本为cglib-nodep-2.2.jar。
cglib的应用是非常多的,但是当我们使用它的时候,如果一不小心,等出了问题再去查,就比较杯具了。所以最好的解决方案就是写代码时就注意这些细节。(当然了,不能指望在开发阶段不引入Bug)
近期项目在做压力测试,暴露了内存泄漏的Bug,cglib的使用不当便是原因之一。
下面来介绍代码。
清单1:
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>
 *
*/
publicclass CglibLeak1 {

public<T> T newProxyInstance(Class<T> clazz) {
return newProxyInstance(clazz, new MyInterceptor(), new MyFilter());
 }

/** *//**
 * 创建一个类动态代理.
 * 
* @param <T>
 * @param superclass
 * @param methodCb
 * @param callbackFilter
 * @return
*/
publicstatic<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();
 }

/** *//**
 * 实现MethodInterceptor接口
 * 
* @author l
 *
*/
class MyInterceptor implements MethodInterceptor {
 @Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
throws Throwable {
returnnull;
 }
 }

/** *//**
 * 实现CallbackFilter接口
 * 
* @author l
*/
class MyFilter implements CallbackFilter {
 @Override
publicint accept(Method method) {
// Do some thing
return1;
 }
 }

/** *//**
 * 测试代码
 * @param args
 * @throws InterruptedException
*/
publicstaticvoid main(String args[]) throws InterruptedException {
 CglibLeak1 leak =new CglibLeak1();
int count =0;
while(true) {
 leak.newProxyInstance(Object.class); // 为了测试缩写
 Thread.sleep(100);
 System.out.println(count++);
 }
 }
}


用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>
*/
publicclass CglibLeak {

privatestatic MethodInterceptor myInterceptor =new MethodInterceptor() {
 @Override
public Object intercept(Object obj,
 Method method, Object[] args,
 MethodProxy proxy) throws Throwable {
// do some things
returnnull;
 }
 };
// 创建实例
privatestatic CallbackFilter myFilter =new MyFilter();

publicstatic<T> T newProxyInstance(Class<T> clazz) {
return newProxyInstance(clazz, myInterceptor, myFilter);
 }

/** *//**
 * 实现CallbackFilter接口
 *
 * @author l
*/
staticclass MyFilter implements CallbackFilter {
 @Override
publicint accept(Method method) {
// Do some thing
return1;
 }
 }

/** *//**
 * 创建一个类动态代理.
 *
 * @param <T>
 * @param superclass
 * @param methodCb
 * @param callbackFilter
 * @return
*/
publicstatic<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
*/
publicstaticvoid 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内存问题无处不在啊
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值