这几天我在我的项目中添加了一束代码,Gradle构建在Github操作中失败,一些测试在运行Gradle测试任务时抛出OOM错误。
项目技术栈是Spring Boot 3/R2dbc + Kotlin 1.8/Kotlin Coroutines+ Java 17(Gradle Java语言级别设置为17)
构建工具栈。
- 本地系统:Windows 10 Pro(16G内存)/Oracle JDK 17/Gradle 7.6(项目Gradle包装器)
- Github actions: Custom Ubuntu with 16G memory/Amazon JDK 17
经过研究,我们使用了一个自定义的更大的运行器,内存为16G,并将Gradle JVM堆大小增加到8G,但这没有帮助。
org.gradle.jvmargs=-Xmx8g -Xms4g
在运行测试时,我们仍然得到以下错误。但是测试代码本身不是问题,它们已经在我的本地机器上通过了。
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message can't create name string at src/java.instrument/share/native/libinstrument/JPLISAgent.c line: 827
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "ClassGraph-worker-439"
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "ClassGraph-worker-438"
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "boundedElastic-evictor-1"
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "ClassGraph-worker-435"
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message can't create name string at src/java.instrument/share/native/libinstrument/JPLISAgent.c line: 827
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "ClassGraph-worker-433"
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "ClassGraph-worker-436"
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "ClassGraph-worker-432"
在Spring Boot和其他讨论中发布了这个问题后,现在确认是由classgraph引起的。spring doc使用Classgraph来扫描和分析OpenAPI端点。如果我从项目中删除spring文档,它又可以工作了。
问题是,即使我设置了一个全局springdoc.packageToScan
来缩小扫描范围,它仍然失败与OOM错误。
看起来这个错误发生在Gradle worker中。Gradle执行单独的JVM进程来运行测试,其内存设置与Gradle主进程不同。默认使用512mb
你可以做不同的事情来解决这个问题:要么增加工作线程的堆,要么减少每个工作线程中执行的测试量。您可以通过两种方式减少这个数量:为每个模块分支多个并行进程,或者在串行模式下为每个固定数量的测试分支一个新进程。增加Gradle测试worker的堆可能是最好的,但是如果你有许多模块并行地执行测试,你也可能耗尽代理的总内存。
请查看Gradle测试文档,了解这些选项的更多细节。您可以使用下面的代码()来控制所有这些设置。我不建议同时应用,这只是为了说明选项)。
tasks.withType<Test>().configureEach {
maxHeapSize = "1g"
forkEvery = 100
maxParallelForks = 4
}
在任何情况下,我的建议是对构建进行概要分析,以准确地找出哪些进程耗尽了内存,什么是最合适的内存设置,并潜在地确定产生这种情况的泄漏在哪里。