Groovy 脚本类加载卸载
大量加载类可能导致元空间OOM。首先回顾下类卸载需要满足的条件,要同时满足以下3个条件:
- 该类的实例全部被回收
- 加载该类的ClassLoader被回收
- 该Class对象没在任何地方引用,也无法使用反射来访问。
添加类加载日志,限制元空间大小,验证类是否卸载:-XX:+TraceClassLoading -XX:+TraceClassUnloading -XX:MaxMetaspaceSize=30m
下面这段代码能正常卸载BaseValue类:
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
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
testLoader();
}
}
//
@SneakyThrows
public static void testLoader() {
GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
String hello = "package com.example.cachedemo\n" +
"\n" +
"import cn.hutool.core.util.IdUtil\n" +
"\n" +
"class BaseValue {\n" +
" def map = [\"abc.a\": \"1\", \"abc.b\": \"2\", \"abc.c\": IdUtil.getSnowflake().nextIdStr()]\n" +
"}";
Class aClass = groovyClassLoader.parseClass(hello);
try {
GroovyObject object = (GroovyObject) aClass.getConstructor().newInstance();
Map<String, Object> map = (Map<String, Object>)object.getProperty("map");
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}下面这个也能正常卸载,是因为aClass的类加载器实际是groovy.lang.GroovyClassLoader$InnerLoader,每次parseClass都使用一个新的groovy.lang.GroovyClassLoader$InnerLoader。所以testLoader结束后类可回收。
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
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
testLoader();
}
// 数量是1, 暂且不清楚是为什么。
System.out.println(groovyClassLoader.getLoadedClasses().length);
}
public static GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
@SneakyThrows
public static void testLoader() {
String hello = "package com.example.cachedemo\n" +
"\n" +
"import cn.hutool.core.util.IdUtil\n" +
"\n" +
"class BaseValue {\n" +
" def map = [\"abc.a\": \"1\", \"abc.b\": \"2\", \"abc.c\": IdUtil.getSnowflake().nextIdStr()]\n" +
"}";
Class aClass = groovyClassLoader.parseClass(hello);
try {
GroovyObject object = (GroovyObject) aClass.getConstructor().newInstance();
Map<String, Object> map = (Map<String, Object>)object.getProperty("map");
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}不能正常卸载
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
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
testLoader();
}
// 数量是1
System.out.println(groovyClassLoader.getLoadedClasses().length);
}
public static List l = new ArrayList();
@SneakyThrows
public static void testLoader() {
String hello = "package com.example.cachedemo\n" +
"\n" +
"import cn.hutool.core.util.IdUtil\n" +
"\n" +
"class BaseValue {\n" +
" def map = [\"abc.a\": \"1\", \"abc.b\": \"2\", \"abc.c\": IdUtil.getSnowflake().nextIdStr()]\n" +
"}";
Class aClass = groovyClassLoader.parseClass(hello);
l.add(aClass);
try {
GroovyObject object = (GroovyObject) aClass.getConstructor().newInstance();
Map<String, Object> map = (Map<String, Object>)object.getProperty("map");
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}