线程上下文类加载器ContextClassLoader内存泄漏隐患 (2)

Thread在初始化实例(调用new Thread())的时候一定会调用Thread#init()方法,新建的子线程实例会继承父线程的contextClassLoader属性,而应用主线程[main]的contextClassLoader一般是应用类加载器(Application ClassLoader,有时也称为系统类加载器),其他用户线程都是主线程派生出来的后代线程,如果不覆盖contextClassLoader,那么新建的后代线程的contextClassLoader就是应用类加载器。

分析到这里,笔者只想说明一个结论:后代线程的线程上下文类加载器会继承父线程的线程上下文类加载器,其实这里用继承这个词语也不是太准确,准确来说应该是后代线程的线程上下文类加载器和父线程的上下文类加载器完全相同,如果都派生自主线程,那么都是应用类加载器。对于这个结论可以验证一下(下面例子在JDK8中运行):

public class ThreadContextClassLoaderMain { public static void main(String[] args) throws Exception { AtomicReference<Thread> grandSonThreadReference = new AtomicReference<>(); Thread sonThread = new Thread(() -> { Thread thread = new Thread(()-> {},"grand-son-thread"); grandSonThreadReference.set(thread); }, "son-thread"); sonThread.start(); Thread.sleep(100); Thread main = Thread.currentThread(); Thread grandSonThread = grandSonThreadReference.get(); System.out.println(String.format("ContextClassLoader of [main]:%s", main.getContextClassLoader())); System.out.println(String.format("ContextClassLoader of [%s]:%s",sonThread.getName(), sonThread.getContextClassLoader())); System.out.println(String.format("ContextClassLoader of [%s]:%s", grandSonThread.getName(), grandSonThread.getContextClassLoader())); } }

控制台输出如下:

ContextClassLoader of [main]:sun.misc.Launcher$AppClassLoader@18b4aac2 ContextClassLoader of [son-thread]:sun.misc.Launcher$AppClassLoader@18b4aac2 ContextClassLoader of [grand-son-thread]:sun.misc.Launcher$AppClassLoader@18b4aac2

印证了前面的结论,主线程、子线程、孙子线程的线程上下文类加载器都是AppClassLoader类型,并且指向同一个实例sun.misc.Launcher$AppClassLoader@18b4aac2。

ContextClassLoader设置不当导致内存泄漏的隐患

只要有大量热加载和卸载动态类的场景,就需要警惕后代线程ContextClassLoader设置不当导致内存泄漏。画个图就能比较清楚:

线程上下文类加载器ContextClassLoader内存泄漏隐患

父线程中设置了一个自定义类加载器,用于加载动态类,子线程新建的时候直接使用了父线程的自定义类加载器,导致该自定义类加载器一直被子线程强引用,结合前面的类卸载条件分析,所有由该自定义类加载器加载出来的动态类都不能被卸载,导致了内存泄漏。这里还是基于文章前面的那个例子做改造:

新增一个线程X用于进行类加载,新建一个自定义类加载器,设置线程X的上下文类加载器为该自定义类加载器。

线程X运行方法中创建一个新线程Y,用于接收类加载成功的事件并且进行打印。

public interface HelloService { String sayHello(); BlockingQueue<String> CLASSES = new LinkedBlockingQueue<>(); BlockingQueue<String> EVENTS = new LinkedBlockingQueue<>(); AtomicBoolean START = new AtomicBoolean(false); static void main(String[] args) throws Exception { Thread thread = new Thread(() -> { ClassLoader loader = new ClassLoader() { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String location = "I:\\DefaultHelloService1.class"; if (name.contains("DefaultHelloService2")) { location = "I:\\DefaultHelloService2.class"; } File classFile = new File(location); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { InputStream stream = new FileInputStream(classFile); int b; while ((b = stream.read()) != -1) { outputStream.write(b); } } catch (IOException e) { throw new IllegalArgumentException(e); } byte[] bytes = outputStream.toByteArray(); Class<?> defineClass = super.defineClass(name, bytes, 0, bytes.length); try { EVENTS.put(String.format("加载类成功,类名:%s", defineClass.getName())); } catch (Exception ignore) { } return defineClass; } }; Thread x = new Thread(() -> { try { if (START.compareAndSet(false, true)) { Thread y = new Thread(() -> { try { for (; ; ) { String event = EVENTS.take(); System.out.println("接收到事件,事件内容:" + event); } } catch (Exception ignore) { } }, "Y"); y.setDaemon(true); y.start(); } for (; ; ) { String take = CLASSES.take(); Class<?> klass = loader.loadClass(take); HelloService helloService = (HelloService) klass.newInstance(); System.out.println(helloService.sayHello()); } } catch (Exception ignore) { } }, "X"); x.setContextClassLoader(loader); x.setDaemon(true); x.start(); }); thread.start(); CLASSES.put("club.throwable.loader.DefaultHelloService1"); CLASSES.put("club.throwable.loader.DefaultHelloService2"); Thread.sleep(5000); System.gc(); Thread.sleep(5000); System.gc(); Thread.sleep(Long.MAX_VALUE); } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpppgy.html