ALL is Well!

敏捷是一条很长的路,摸索着前进着

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  30 随笔 :: 23 文章 :: 71 评论 :: 0 Trackbacks

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
posted on 2010-09-09 17:00 李 明 阅读(3663) 评论(1)  编辑  收藏

评论

# re: cglib使用不慎引发的Java内存泄漏 2010-09-09 20:16 pxb

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Object.class);
enhancer.setCallbacks(new Callback[]{methodCb, NoOp.INSTANCE});
enhancer.setCallbackFilter(callbackFilter);

while (true) {
enhancer.create();
Thread.sleep(100);
System.out.println(count++);
}  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: