cglib版本為cglib-nodep-2.2.jar.
本次只為演示在使用中出現(xiàn)的Java內(nèi)存泄漏的問(wèn)題,以及如何解決這樣的問(wèn)題。
cglib的應(yīng)用是非常多的,但是當(dāng)我們使用它的時(shí)候,如果一不小心,等出了問(wèn)題再去查,就比較杯具了。所以最好的解決方案就是寫(xiě)代碼時(shí)就注意這些細(xì)節(jié)。(當(dāng)然了,不能指望在開(kāi)發(fā)階段不引入Bug)
近期項(xiàng)目在做壓力測(cè)試,暴露了內(nèi)存泄漏的Bug,cglib的使用不當(dāng)便是原因之一。
下面來(lái)介紹代碼。
清單1:
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
* 步驟方法攔截器.<br>
14
*
15
*/
16
public class CglibLeak1 {
17
18
public <T> T newProxyInstance(Class<T> clazz) {
19
return newProxyInstance(clazz, new MyInterceptor(), new MyFilter());
20
}
21
22
/**
23
* 創(chuàng)建一個(gè)類(lèi)動(dòng)態(tài)代理.
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
* 實(shí)現(xiàn)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
* 實(shí)現(xiàn)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
* 測(cè)試代碼
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); // 為了測(cè)試縮寫(xiě)
77
Thread.sleep(100);
78
System.out.println(count++);
79
}
80
}
81
}

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

用JProfiler來(lái)觀(guān)察內(nèi)存對(duì)象情況。
運(yùn)行了一段時(shí)間(幾十秒鐘吧),內(nèi)存對(duì)象的情況如圖所示:

我們看到 MyFilter 的Instance count 已經(jīng)達(dá)到了1266個(gè),而且隨著程序的繼續(xù)運(yùn)行,Instance count還在不斷飆升,此情此景讓人心寒。
而且在JProfiler上點(diǎn)擊 Run GC 按鈕 這些對(duì)象并不會(huì)被回收。內(nèi)存泄漏啦。
原因就是cglib自身的內(nèi)部代理類(lèi)緩存,將MyFilter對(duì)象加入到了緩存中,以至于該對(duì)象很大、并發(fā)量很大時(shí),會(huì)造成內(nèi)存溢出的Bug。
既然知道了原因,解決辦法就很明顯了。
1.重寫(xiě)MyFilter類(lèi)的equals和hashCode方法,這樣,當(dāng)MyFilter對(duì)象準(zhǔn)備進(jìn)入緩存時(shí),cglib會(huì)判斷是否為不同的MyFilter對(duì)象,如果是才加入到緩存。
我們重寫(xiě)了equals和hashCode后,讓cglib認(rèn)為這些MyFilter對(duì)象都是相同的。
2.將MyFilter類(lèi)設(shè)置為靜態(tài)類(lèi)。原理都是相同的。
我以第二種解決方案來(lái)修改代碼,請(qǐng)看。
清單2:
















































































運(yùn)行后的結(jié)果應(yīng)該很明顯了:

MyFilter的Instance count 一直為1.
問(wèn)題解決了。
因?yàn)槲业腗yFilter類(lèi)中沒(méi)有成員變量,所以在多線(xiàn)程并發(fā)訪(fǎng)問(wèn)時(shí)也不會(huì)出現(xiàn)問(wèn)題。
如果以方案1 來(lái)解決這個(gè)內(nèi)存泄漏問(wèn)題情況是怎樣的呢?
清單3:




























































































運(yùn)行一段時(shí)間后(幾十秒),JProfiler的觀(guān)測(cè)結(jié)果為:

MyFilter的對(duì)象還是很多,這是不是就表明 內(nèi)存泄漏的問(wèn)題依然存在呢。
當(dāng)然不是,因?yàn)镴VM垃圾回收策略的原因,我們new出來(lái)的MyFilter對(duì)象并不是 一旦成為垃圾就立即 被回收的。
經(jīng)觀(guān)察,當(dāng)Instance count 為600左右時(shí),GC會(huì)將這些“垃圾”回收。
解決問(wèn)題之際感慨一下:Java內(nèi)存問(wèn)題無(wú)處不在啊。
本文為原創(chuàng),歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明出處BlogJava。