概述
现象是有一些类加载器是作为key放到WeakHashMap里的,但是经历过多次full gc之后,依然坚挺地存在内存里,但是从代码上来说这些类加载器是应该被回收的,因为没有任何强引用可以到达这些类加载器了,于是我们做了内存dump,分析了下内存,发现除了一个WeakHashMap外并没有别的GC ROOT途径达到这些类加载器了,那这样一来经过多次FULL GC肯定是可以被回收的,但是事实却不是这样,为了让这个问题听起来更好理解,还是照例先上个Demo,完全模拟了这种场景。
Demo
首先我们创建两个类AAA和AAB,分别打包到两个不同jar里,比如AAA.jar和AAB.jar,这两个类之间是有关系的,AAA里有个属性是AAB类型的,注意这两个jar不要放到classpath里让appClassLoader加载到:
//+微信:xuanwo008领大厂面试题 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里加载这个类
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,一些说明都写在类里了:
//+微信:xuanwo008领大厂面试题 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){ e.printStackTrace(); } } }
运行的时候请跑在JDK8下,打个断点