如何增大Spock Framework堆内存 解决Gradle测试Java堆溢出问题
问题原因
- 你之前配置的
GROOVY_OPTS、gradlew脚本里的DEFAULT_JVM_OPTS都不会作用于Gradle的测试进程:GROOVY_OPTS仅对groovy命令直接启动的Groovy程序生效,DEFAULT_JVM_OPTS仅对Gradle主进程以及run这类继承Gradle JVM参数的任务生效,而test任务默认会fork独立的JVM进程执行测试,有自己独立的JVM参数配置,默认堆内存通常只有512M,所以你之前的内存配置完全没生效。 - 你的代码本身初始化时就会占用大量内存:
new BitSet(Integer.MAX_VALUE)会在初始化时直接分配足够存储2^31-1位的底层long数组,单个BitSet初始化就占256MB,两个BitSet加起来固定占512MB,再算上JVM基础开销、Spock框架的内存占用,很容易打满测试进程默认的堆内存触发OOM。 - 你执行
./gradlew run能正常运行,是因为run任务拿到了你配置的2G堆内存,足够支撑这两个BitSet的初始化开销。
解决方案
方案1:给测试进程配置足够堆内存(快速修复)
在项目的build.gradle中为test任务单独配置JVM内存参数,和你run任务的内存规格对齐:
如果使用Groovy DSL(默认的build.gradle格式):
test { // 给测试进程分配2G堆内存 maxHeapSize = "2048m" // 避免同时fork多个测试进程占满本机内存,可根据机器实际内存调整 forkEvery = 1 }
如果使用Kotlin DSL(build.gradle.kts格式):
tasks.test { maxHeapSize = "2048m" forkEvery = 1 }
方案2:优化BitSet初始化逻辑(根本优化,推荐)
你当前给BitSet构造传Integer.MAX_VALUE是完全没必要的:BitSet本身支持动态扩容,无参构造默认只初始化64位的极小空间,后续调用set()方法存储数据时会根据需要自动扩容,不会影响功能。去掉构造参数后,初始化阶段内存占用从固定512MB降到几字节,只有当你真的存储接近全量int值的时候才会逐步扩容到最大内存,平时跑测试、处理少量IP数据的时候内存压力会极小:
public class IntSet { // 移除BitSet构造的Integer.MAX_VALUE参数,使用默认无参构造 private final BitSet[] storage = {new BitSet(), new BitSet()}; public void add(int i) { if (i >= 0) { storage[0].set(i); } else { storage[1].set(-++i); } } public long size() { return (long) storage[0].cardinality() + storage[1].cardinality(); } }
优化后哪怕测试进程用默认的512M堆也能正常运行,不需要额外调大内存。
内容的提问来源于stack exchange,提问作者Jegors Čemisovs
相关产品推荐
相关产品推荐

