概述
这篇文章基于最近在排查的一个问题,花了我们团队不少时间来排查这个问题,现象是有一些类加载器是作为key放到WeakHashMap里的,但是经历过多次full gc之后,依然坚挺地存在内存里,但是从代码上来说这些类加载器是应该被回收的,因为没有任何强引用可以到达这些类加载器了,于是我们做了内存dump,分析了下内存,发现除了一个WeakHashMap外并没有别的GC ROOT途径达到这些类加载器了,那这样一来经过多次FULL GC肯定是可以被回收的,但是事实却不是这样,为了让这个问题听起来更好理解,还是照例先上个Demo,完全模拟了这种场景。
Demo
首先我们创建两个类AAA和AAB,分别打包到两个不同jar里,比如AAA.jar和AAB.jar,这两个类之间是有关系的,AAA里有个属性是AAB类型的,注意这两个jar不要放到classpath里让appClassLoader加载到:
1
2
3
4
5
6
7
8
9
10
11
|
public class AAA {
private AAB aab;
public AAA(){
aab=new AAB();
}
public void clear(){
aab=null;
}
}
public class AAB {}
|
接着我们创建一个类加载TestLoader,里面存一个WeakHashMap,专门来存TestLoader的,并且复写loadClass方法,如果是加载AAB这个类,就创建一个新的TestLoader来从AAB.jar里加载这个类
1
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
|
import java.net.URL;
import java.net.URLClassLoader;
import java.util.WeakHashMap;
public class TestLoader extends URLClassLoader {
public static WeakHashMap<TestLoader,Object> map=new WeakHashMap<TestLoader,Object>();
private static int count=0;
public TestLoader(URL[] urls){
super(urls);
map.put(this, new Object());
}
@SuppressWarnings("resource")
public Class<?> loadClass(String name) throws ClassNotFoundException {
if(name.equals("AAB") && count==0){
try {
count=1;
URL[] urls = new URL[1];
urls[0] = new URL("file:///home/nijiaben/tmp/AAB.jar");
return new TestLoader(urls).loadClass("AAB");
}catch (Exception e){
e.printStackTrace();
}
}else{
return super.loadClass(name);
}
return null;
}
}
|
再看我们的主类TTest,一些说明都写在类里了:
1
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
|
import java.lang.reflect.Method;
import java.net.URL;
/**
* Created by nijiaben on 4/22/16.
*/
public class TTest {
private Object aaa;
public static void main(String args[]){
try {
TTest tt = new TTest();
//将对象移到old,并置空aaa的aab属性
test(tt);
//清理掉aab对象
System.gc();
System.out.println("finished");
}catch (Exception e){
e.printStackTrace();
}
}
@SuppressWarnings("resource")
public static void test(TTest tt){
try {
//创建一个新的类加载器,从AAA.jar里加载AAA类
URL[] urls = new URL[1];
urls[0] = new URL("file:///home/nijiaben/tmp/AAA.jar");
tt.aaa=new TestLoader(urls).loadClass("AAA").newInstance();
//保证类加载器对象能进入到old里,因为ygc是不会对classLoader做清理的
for(int i=0;i<10;i++){
System.gc();
Thread.sleep(1000);
}
//将aaa里的aab属性清空掉,以便在后面gc的时候能清理掉aab对象,这样AAB的类加载器其实就没有什么地方有强引用了,在full gc的时候能被回收
Method[] methods=tt.aaa.getClass().getDeclaredMethods();
for(Method m:methods){
if(m.getName().equals("clear")){
m.invoke(tt.aaa);
break;
}
}
}catch (Exception e){
|