52 如何 尽可能的减少 自定义ClassLoader 造成的影响
Posted 蓝风9
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了52 如何 尽可能的减少 自定义ClassLoader 造成的影响相关的知识,希望对你有一定的参考价值。
前言
// 呵呵 很快又该总结 2022 了, 希望这一年也能总结出很多收获
接着 java.lang.Class/java.lang.ClassLoader/InstanceKlass/ClassloaderData 的卸载
可以先看一下 这一篇文章, 明确一下 上下文
这里 主要说的是 如果我们的场景中存在自定义的 classloader 的情况下
应该 怎么尽可能少的减小 自定义classloader 的存在造成潜在的 内存泄漏的风险
第一是 classloader 的数量的控制, 其次是 一些代码层面的一些控制, 需要明确
另外 在文章 java.lang.Class/java.lang.ClassLoader/InstanceKlass/ClassloaderData 的卸载 中, 还有一些 可以看到的疑问, 没有提到的, 这里一起看一下
以下测试用例基于 jdk8, 部分截图基于 jdk9
测试用例
测试用例, 这里暂时使用第一个版本的测试用例, 我们再迭代
/**
* Test29MultiLoaderContextInvoker
*
* @author Jerry.X.He <970655147@qq.com>
* @version 1.0
* @date 2021-12-19 09:31
*/
public class Test29MultiLoaderContextInvoker
// Test29MultiLoaderContextInvoker
// -Xmx10M -XX:+UseSerialGC -XX:+TraceClassLoading
// -Xmx10M -XX:+UseSerialGC -XX:+TraceClassUnloading
public static void main(String[] args) throws Exception
ClassLoader appClassloader = Test29MultiLoaderContextInvoker.class.getClassLoader();
String[] alwaysParentPatterns = new String[];
URL[] classpathes = new URL[]
new File("/Users/jerry/IdeaProjects/HelloWorld/target/classes").toURI().toURL()
;
Consumer<Throwable> throwableConsumer = (ex) ->
ex.printStackTrace();
;
int loopCount = 20;
for (int i = 0; i < loopCount; i++)
ChildFirstClassLoader classLoader = new ChildFirstClassLoader(classpathes, appClassloader, alwaysParentPatterns, throwableConsumer);
Class mainClass = classLoader.loadClass("com.hx.test12.Test29MultiLoaderContextMain");
Method mainMethod = mainClass.getDeclaredMethod("main", int.class, String[].class);
mainMethod.invoke(null, i, args);
/**
* Test29MultiLoaderContextInvoker
*
* @author Jerry.X.He <970655147@qq.com>
* @version 1.0
* @date 2021-12-19 09:31
*/
public class Test29MultiLoaderContextMain
// hold 1M
public static byte[] dummyBytes = new byte[1 * 1024 * 1024];
// Test29MultiLoaderContextInvoker
public static void main(int idx, String[] args) throws Exception
System.out.println(String.format(" Test29MultiLoaderContextMain.main be invoked, idx : %s, args : %s ", idx, args));
new Thread(() ->
try
System.out.println(String.format(" bizThread - %s is started ", idx));
Thread.sleep(10_000);
System.out.println(String.format(" bizThread - %s is end ", idx));
catch (Exception e)
e.printStackTrace();
).start();
ChildFirstClassLoader 的引用有哪些 ?
如下, 首先要查询的是 classloader 加载了那些东西, 这里可以看到的是 只是加载了 com.hx.test12.Test29MultiLoaderContextMain
但是 实际不然 其实还有 com.hx.test12.Test29MultiLoaderContextMain 里面的这一个内部类
com.hx.test12.Test29MultiLoaderContextMain 的类型是没有实例的
com.hx.test12.Test29MultiLoaderContextMain$$Lambda$3/1032000752 类型存在一个实例, 传递给了 Thread-$N
其次是当前 Thread 上下文的一些引用
这里的 target 就是我们上面提到的 com.hx.test12.Test29MultiLoaderContextMain$$Lambda$3/1032000752 的实例
我们这里关心的是 inheritedAccessControlContext 中的 context 中的第一个 ProtectionDomain 元素, 可以看到的是 引用了一个 ChildFirstClassLoader
总结一下 我们明显能够看到的有两条路径
1. Thread-$N 关联的 target 是一个 com.hx.test12.Test29MultiLoaderContextMain$$Lambda$3/1032000752 的实例, 其 classloader 为 ChildFirstClassLoader
2. Thread-$N 关联的 inheritedAccessControlContext 中的 context[0] 会关联到 ChildFirstClassLoader
AccessController. inheritedAccessControlContext 是怎么来的?
上面我们可以看到 com.hx.test12.Test29MultiLoaderContextMain$$Lambda$3/1032000752 的实例也会关联 ChildFirstClassLoader, 那么我们这里是需要取消他对 ChildFirstClassLoader 的关联, 这个还是挺好处理的
那么另外一个呢? Thread.inheritedAccessControlContext 呢?
首先我们要看 它的初始化的地方 调用的是 AccessController.getStackAccessControlContext
AccessController.getStackAccessControlContext 是一个 native 方法
实现大致是如下, 从遍历当前线程的堆栈, 分别获取 调用的方法对应的 java.lang.Class 的 ProtectionDomain
分别是 Test29MultiLoaderContextMain 的 ProtectionDomain 和 Test29MultiLoaderContextInvoker 的 ProtectionDomain
呵呵 既然知道了构造的方式, 那么才有 处理的方式
输出一下 其中的两个元素, 大致是
java.security.ProtectionDomain
0x00000007bf795450 - klass: 'java/security/ProtectionDomain'
- ---- fields (total size 5 words):
- private 'hasAllPerm' 'Z' @12 false
- private final 'staticPermissions' 'Z' @13 false
- private 'codesource' 'Ljava/security/CodeSource;' @16 a 'java/security/CodeSource'0x00000007bf78d5c0 (f7ef1ab8 f7ef168a)
- private 'classloader' 'Ljava/lang/ClassLoader;' @20 a 'org/apache/flink/util/ChildFirstClassLoader'0x00000007bf78b450 (f7ef168a f7ef2a91)
- private 'principals' '[Ljava/security/Principal;' @24 a 'java/security/Principal'[0] 0x00000007bf795488 (f7ef2a91 f7ef1af6)
- private 'permissions' 'Ljava/security/PermissionCollection;' @28 a 'java/security/Permissions'0x00000007bf78d7b0 (f7ef1af6 f7ef2a8f)
- final 'key' 'Ljava/security/ProtectionDomain$Key;' @32 a 'java/security/ProtectionDomain$Key'0x00000007bf795478 (f7ef2a8f 0)
java.security.ProtectionDomain
0x00000007bf8bb638 - klass: 'java/security/ProtectionDomain'
- ---- fields (total size 5 words):
- private 'hasAllPerm' 'Z' @12 false
- private final 'staticPermissions' 'Z' @13 false
- private 'codesource' 'Ljava/security/CodeSource;' @16 a 'java/security/CodeSource'0x00000007bf8c1518 (f7f182a3 f7f378cb)
- private 'classloader' 'Ljava/lang/ClassLoader;' @20 a 'jdk/internal/loader/ClassLoaders$AppClassLoader'0x00000007bf9bc658 (f7f378cb f7f182a8)
- private 'principals' '[Ljava/security/Principal;' @24 a 'java/security/Principal'[0] 0x00000007bf8c1540 (f7f182a8 f7f182aa)
- private 'permissions' 'Ljava/security/PermissionCollection;' @28 a 'java/security/Permissions'0x00000007bf8c1550 (f7f182aa f7f182ae)
- final 'key' 'Ljava/security/ProtectionDomain$Key;' @32 a 'java/security/ProtectionDomain$Key'0x00000007bf8c1570 (f7f182ae 0)
去掉 ChildFirstClassLoader 的两个引用
调整 Test29MultiLoaderContextInvoker 如下
第一个是 新建 MyRunnable 的地方是 Test29MultiLoaderContextInvoker 中, 对应的 classloader 为 AppClassLoader
第二个是 新建 Thread 的地方也放在了 Test29MultiLoaderContextInvoker, 这样 Thread-$N 的 inheritedAccessControlContext 的 context 中就只会有一个 ProtectionDomain 关联的 classloader 为 AppClassLoader
/**
* Test29MultiLoaderContextInvoker
*
* @author Jerry.X.He <970655147@qq.com>
* @version 1.0
* @date 2021-12-19 09:31
*/
public class Test29MultiLoaderContextInvoker
// Test29MultiLoaderContextInvoker
// -Xmx10M -XX:+UseSerialGC -XX:+TraceClassLoading
// -Xmx10M -XX:+UseSerialGC -XX:+TraceClassUnloading
public static void main(String[] args) throws Exception
ClassLoader appClassloader = Test29MultiLoaderContextInvoker.class.getClassLoader();
String[] alwaysParentPatterns = new String[];
URL[] classpathes = new URL[]
new File("/Users/jerry/IdeaProjects/HelloWorld/target/classes").toURI().toURL()
;
Consumer<Throwable> throwableConsumer = (ex) ->
ex.printStackTrace();
;
int loopCount = 20;
for (int i = 0; i < loopCount; i++)
ChildFirstClassLoader classLoader = new ChildFirstClassLoader(classpathes, appClassloader, alwaysParentPatterns, throwableConsumer);
Class mainClass = classLoader.loadClass("com.hx.test12.Test29MultiLoaderContextMain");
Thread runnable = new Thread(new MyRunnable());
Method mainMethod = mainClass.getDeclaredMethod("main", int.class, Thread.class, String[].class);
mainMethod.invoke(null, i, runnable, args);
/**
* MyRunnable
*
* @author Jerry.X.He
* @version 1.0
* @date 2021-12-26 14:55
*/
private static class MyRunnable implements Runnable
@Override
public void run()
try
int idx = 0;
System.out.println(String.format(" bizThread - %s is started ", idx));
Thread.sleep(10_000);
System.out.println(String.format(" bizThread - %s is end ", idx));
catch (Exception e)
e.printStackTrace();
调整 Test29MultiLoaderContextMain 如下
/**
* Test29MultiLoaderContextInvoker
*
* @author Jerry.X.He <970655147@qq.com>
* @version 1.0
* @date 2021-12-19 09:31
*/
public class Test29MultiLoaderContextMain
// hold 1M
public static byte[] dummyBytes = new byte[1 * 1024 * 1024];
// Test29MultiLoaderContextInvoker
public static void main(int idx, Thread runnable, String[] args) throws Exception
System.out.println(String.format(" Test29MultiLoaderContextMain.main be invoked, idx : %s, args : %s ", idx, args));
runnable.start();
看一下 是否符合我们上面的期望
然后 重新执行, 日志如下, 可以看到, 在执行 20 个循环的过程中存在 Test29MultiLoaderContextMain 的卸载, 这个就是 我们期望达到的结果
Test29MultiLoaderContextMain.main be invoked, idx : 0, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
Test29MultiLoaderContextMain.main be invoked, idx : 1, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
Test29MultiLoaderContextMain.main be invoked, idx : 2, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
Test29MultiLoaderContextMain.main be invoked, idx : 3, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
Test29MultiLoaderContextMain.main be invoked, idx : 4, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
[Unloading class com.hx.test12.Test29MultiLoaderContextMain 0x00000007c0098828]
[Unloading class com.hx.test12.Test29MultiLoaderContextMain 0x00000007c0098028]
[Unloading class com.hx.test12.Test29MultiLoaderContextMain 0x00000007c0097828]
[Unloading class com.hx.test12.Test29MultiLoaderContextMain 0x00000007c0097028]
[Unloading class com.hx.test12.Test29MultiLoaderContextMain 0x00000007c007e828]
Test29MultiLoaderContextMain.main be invoked, idx : 5, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
Test29MultiLoaderContextMain.main be invoked, idx : 6, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
Test29MultiLoaderContextMain.main be invoked, idx : 7, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
Test29MultiLoaderContextMain.main be invoked, idx : 8, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
Test29MultiLoaderContextMain.main be invoked, idx : 9, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
Test29MultiLoaderContextMain.main be invoked, idx : 10, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
[Unloading class com.hx.test12.Test29MultiLoaderContextMain 0x00000007c007e828]
[Unloading class com.hx.test12.Test29MultiLoaderContextMain 0x00000007c0097028]
[Unloading class com.hx.test12.Test29MultiLoaderContextMain 0x00000007c0097828]
[Unloading class com.hx.test12.Test29MultiLoaderContextMain 0x00000007c0098028]
[Unloading class com.hx.test12.Test29MultiLoaderContextMain 0x00000007c0098828]
[Unloading class com.hx.test12.Test29MultiLoaderContextMain 0x00000007c0099028]
Test29MultiLoaderContextMain.main be invoked, idx : 11, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
Test29MultiLoaderContextMain.main be invoked, idx : 12, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
Test29MultiLoaderContextMain.main be invoked, idx : 13, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
Test29MultiLoaderContextMain.main be invoked, idx : 14, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
Test29MultiLoaderContextMain.main be invoked, idx : 15, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
Test29MultiLoaderContextMain.main be invoked, idx : 16, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
[Unloading class com.hx.test12.Test29MultiLoaderContextMain 0x00000007c0098828]
[Unloading class com.hx.test12.Test29MultiLoaderContextMain 0x00000007c0098028]
[Unloading class com.hx.test12.Test29MultiLoaderContextMain 0x00000007c0097828]
[Unloading class com.hx.test12.Test29MultiLoaderContextMain 0x00000007c0097028]
[Unloading class com.hx.test12.Test29MultiLoaderContextMain 0x00000007c007e828]
[Unloading class com.hx.test12.Test29MultiLoaderContextMain 0x00000007c0099828]
Test29MultiLoaderContextMain.main be invoked, idx : 17, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
Test29MultiLoaderContextMain.main be invoked, idx : 18, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
Test29MultiLoaderContextMain.main be invoked, idx : 19, args : [Ljava.lang.String;@2bea5ab4
bizThread - 0 is started
// after 10s
bizThread - 0 is end
bizThread - 0 is end
bizThread - 0 is end
bizThread - 0 is end
bizThread - 0 is end
bizThread - 0 is end
bizThread - 0 is end
bizThread - 0 is end
bizThread - 0 is end
bizThread - 0 is end
bizThread - 0 is end
bizThread - 0 is end
bizThread - 0 is end
bizThread - 0 is end
bizThread - 0 is end
bizThread - 0 is end
bizThread - 0 is end
bizThread - 0 is end
bizThread - 0 is end
bizThread - 0 is end
最终的结论如下
1. 控制 自定义 ClassLoader 的数量, 避免比较麻烦的细节控制
2. 自定义 ClassLoader 加载的 class 需要尽可能的单纯, 少去 access 其他的上下文, 把控的成本很高, 有一定的经验要求
完
参考
java.lang.Class/java.lang.ClassLoader/InstanceKlass/ClassloaderData 的卸载
以上是关于52 如何 尽可能的减少 自定义ClassLoader 造成的影响的主要内容,如果未能解决你的问题,请参考以下文章