使用ClassValue时如何防止ClassLoader泄露?

问题描述 投票:0回答:1

对于我的测试,我使用的是部分用 Groovy 编写的 rest-assured。 在我的应用程序中,我使用自定义

ClassLoader
加载 rest-assured 和 Groovy 类,每次测试后,
ClassLoader
关闭并准备好被 GC 收集。

以防万一,我使用

-Dgroovy.use.classvalue=false
启动我的测试,使用类
GroovyClassValuePreJava7
这是
ClassValue
的 Groovy 实现,
ClassLoader
s 被正确地从堆中移除但是当我使用
-Dgroovy.use.classvalue=true 启动我的测试
,使用类
GroovyClassValueJava7
,它是
ClassValue
的子类型,我最终得到了一个 OOME。

感谢我的分析器,我可以看到每个

ClassLoader
实际上都由JNI全局引用保留到每个原始类(
void.class
float.class
boolean.class
int.class
double.class
long.class
char.class
,
byte.class
).

如果我天真地在这些类中的每一个上调用

InvokerHelper.removeClass(Class)
,以清除
ClassValueMap
,我的
ClassLoaders
不再有任何GC根,但不幸的是,它们仍在堆中。

我的测试类如下:

class ClassLoaderLeakTest {

    ...

    private CustomClassLoader cl;

    @BeforeEach
    void configure() {
        cl = new CustomClassLoader();
    }

    @AfterEach
    void clean() throws Exception {
        cl.close();
        cl = null;
    }

    @RepeatedTest(100)
    void test() throws Exception {
        cl.loadClass("org.example.Main").getMethod("callTest").invoke(null);
    }
}

Main
课简单放心测试:

public class Main {
    public static void callTest() {
        RestAssured.given()
                .when().get("/hello")
                .then()
                .statusCode(200)
                .body(is("hello"));
    }
}

CustomClassLoader
基本上是一个
URLClassLoader
从特定的 lib 文件夹加载 jar 文件:

public class CustomClassLoader extends URLClassLoader {

    public CustomClassLoader() {
        super(urls(), null);
    }

    private static URL[] urls() {
        Function<URI, URL> toURL = uri -> {
            try {
                return uri.toURL();
            } catch (MalformedURLException e) {
                throw new RuntimeException(e);
            }
        };
        List<URL> l = Arrays.stream(
            Objects.requireNonNull(new File("./lib").listFiles())
        )
                .map(File::getAbsoluteFile)
                .map(File::toURI)
                .map(toURL).collect(Collectors.toList());
        l.add(
            toURL.apply(new File("./target/classes").getAbsoluteFile().toURI())
        );
        return l.toArray(URL[]::new);
    }
}

我创建了一个小项目来重现,任何想法/帮助都非常受欢迎。

ClassLoader
泄漏的根本原因是什么?

我还尝试删除对

ClassLoader
s 的软引用、弱引用和虚引用,但它没有改变任何东西,即使没有通往 GC 根的路径,无论引用类型如何,我仍然在堆中保留了
ClassLoader
s .

java groovy memory-leaks classloader rest-assured
1个回答
0
投票

为了修复泄漏,我们需要通过调用

ClassValue
来删除
ClassValue#remove(Class)
的所有现有实例的关联值。

Groovy
中,我发现最简单的方法是获取所有
ClassInfo
并在关闭
InvokerHelper.removeClass(Class)
时在每个基础类上调用
ClassLoader
,如下所示:

for (ClassInfo ci : ClassInfo.getAllClassInfo()) {
    InvokerHelper.removeClass(ci.getTheClass());
}
© www.soinside.com 2019 - 2024. All rights reserved.