Java单元测试实践-00.目录(9万多字文档+700多测试示例)
https://blog.csdn.net/a82514921/article/details/107969340

1. Gradle执行test任务卡死问题解决

1.1. test任务卡死问题现象

使用Gradle test任务执行单元测试时,执行一段时间后卡死,通过testLogging参数指定的测试日志查看,执行了几十个测试类后不再继续执行。

1.1.1. 无效的解决方法

1.1.2. 与Gradle版本的关系

使用Gradle 4.x,5.x,6.x版本,均存在以上问题,说明与Gradle版本无关。

1.1.3. 测试结束后关闭数据库连接池

使用jstack命令查看执行测试的GradleWorkerMain进程的线程堆栈,发现存在几十个名为“Druid-ConnectionPool-Destroy”的线程处于TIMED_WAITING状态,几十个名为“Druid-ConnectionPool-Create”的线程处于WAITING状态,如下所示:

"Druid-ConnectionPool-Destroy-1592700401" #72 daemon prio=5 os_prio=0 tid=0x000000005b795000 nid=0xb248 waiting on condition [0x000000006588f000]java.lang.Thread.State: TIMED_WAITING (sleeping)at java.lang.Thread.sleep(Native Method)at com.alibaba.druid.pool.DruidDataSource$DestroyConnectionThread.run(DruidDataSource.java:1898)"Druid-ConnectionPool-Create-1592700401" #71 daemon prio=5 os_prio=0 tid=0x000000005b794000 nid=0xb0d4 waiting on condition [0x000000006e3af000]java.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for  <0x00000000c3064f40> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)at com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread.run(DruidDataSource.java:1824)

以上现象说明每个测试类执行完毕后,数据库连接未正常关闭。

在测试基类TestMockBase中通过@TestExecutionListeners指定的TestExecutionListener实现类TestCommonExecutionListener中,在当前类的测试方法均执行完毕时,即afterTestMethod()方法中,判断若当前类在执行最后一个@Test方法时(判断当前类已执行的@Test方法次数是否等于当前类的@Test方法数量),调用DruidDataSource类的close()方法关闭数据源。

经过以上修改后执行test任务,使用jstack命令查看线程堆栈,名为“Druid-ConnectionPool-Destroy”“Druid-ConnectionPool-Create”的线程分别只有一个,说明每个测试类执行完毕后,数据库连接已正常关闭。

若不通过以上方式在每个测试类结束后关闭数据源,还可能导致数据库服务器的连接不释放,可能耗尽服务器连接。

增加以上处理后,test任务执行卡死的问题未解决,说明以上处理能够解决测试结束后数据库连接池未关闭的问题,但无法解决test任务执行卡死的问题。

1.1.4. 修改SoftRefLRUPolicyMSPerMB参数

修改Gradle test任务,增加JVM参数“-XX:SoftRefLRUPolicyMSPerMB=”,调整软引用执行垃圾回收的时间策略,增大或减少后,test任务执行卡死的问题未解决。

SoftRefLRUPolicyMSPerMB参数说明可参考 https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html 。

1.2. test任务卡死问题解决过程

1.2.1. 查看内存与GC情况

使用“jstat -gccause [PID] [时间间隔] [次数]”命令查看GradleWorkerMain进程的GC情况,例如“jstat -gccause 20600 1s 0”,当test任务卡死时,执行结果如下所示:

S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT    LGCC                 GCC
0.00   0.00 100.00  99.93  98.68  98.69    404    9.326    41   43.136   52.462 Allocation Failure   Ergonomics
0.00   0.00  78.51  99.86  98.69  98.71    404    9.326    41   44.344   53.670 Allocation Failure   No GC
0.00   0.00 100.00  99.86  98.69  98.71    404    9.326    42   44.344   53.670 Allocation Failure   Ergonomics
0.00   0.00  70.04  99.97  98.70  98.70    404    9.326    42   45.605   54.931 Allocation Failure   No GC
0.00   0.00 100.00  99.97  98.70  98.70    404    9.326    43   45.605   54.931 Allocation Failure   Ergonomics

可以看到Old使用率(O列)与Metaspace使用率(M列)接近100%,FGC执行很频繁,GC的原因为“Allocation Failure”或“Ergonomics”。

使用“jstat -gc [PID] [时间间隔] [次数]”命令查看GradleWorkerMain进程的内存各分区使用情况,当test任务卡死时,执行结果如下所示:

S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
17920.0 18432.0  0.0    0.0   137728.0 137728.0  349696.0   349217.9  1359168.0 1341755.8 169088.0 166882.6    412    9.548  31     28.110   37.658
17920.0 18432.0  0.0    0.0   137728.0 123171.4  349696.0   349370.2  1361472.0 1344177.1 169344.0 167223.2    412    9.548  31     29.112   38.660
17920.0 18432.0  0.0    0.0   137728.0 20468.3   349696.0   349361.2  1363648.0 1346154.5 169728.0 167551.5    412    9.548  32     30.011   39.559

可以看到Metaspace容量(MC列)约为1331.7MB,Metaspace已使用(MU列)约为1314.6MB。

查看操作系统内存,物理内存已接近耗尽。

使用“jmap -heap [PID]”命令,查看GradleWorkerMain进程的内存使用情况,MaxMetaspaceSize为非常大的值,如“MaxMetaspaceSize = 17592186044415 MB”。

也可以使用jconsole工具以图形化方式查看GradleWorkerMain进程的内存使用情况。

当test任务卡死时,Metaspace使用情况如下所示:

Old使用情况如下所示:

1.2.2. 调整Metaspace参数

1.2.2.1. Metaspace相关

  • Metaspace概念

参考 https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/considerations.html 。

在Java Hotspot VM中,Java类具有内部表示形式,称为类元数据。在Java Hotspot VM的早期版本中,类元数据在永久代中分配。在JDK8中,永久代已删除,且类元数据已分配在本机内存中。默认情况下,可用于类元数据的本地内存量是无限的。使用选项MaxMetaspaceSize对用于类元数据的本机内存量设置上限。

  • Metaspace相关参数

当Metaspace已使用空间达到“高水位线”时,会触发垃圾回收。垃圾回收之后,高水位线可能会升高或降低,具体取决于类元数据释放的空间量。为避免过早引起另一次垃圾回收,高水位线值将会增大。高水位线初始值由MetaspaceSize参数值决定。MetaspaceSize参数的默认值与平台有关,从12MB至约20MB。

MetaspaceSize与MaxMetaspaceSize参数的示例为“-XX:MetaspaceSize= -XX:MaxMetaspaceSize=”。

  • Metaspace空间回收

当类元数据相应的Java类被卸载时,类元数据会被释放。Java类卸载是由于垃圾回收导致的,垃圾回收可以促使类卸载并释放类元数据。当用于类元数据的空间达到一定级别(高水位线)时,会引发垃圾回收。

  • Metaspace内存溢出异常

参考 https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/memleaks002.html 。

当类元数据所需的本机内存量超过MaxMetaSpaceSize参数值时,将引发带有详细信息为“MetaSpace”的java.lang.OutOfMemoryError异常。

1.2.2.2. 限制Metaspace大小

Gradle执行test任务时,默认情况下未指定MaxMetaspaceSize参数,Metaspace的最大大小取决于操作系统可用内存。如前所述,不限制Metaspace大小时,可能导致操作系统内存耗尽。

使用MaxMetaspaceSize参数限制Metaspace大小为256MB,再执行Gradle test任务。

使用“jstat -gccause”命令查看GradleWorkerMain进程的GC情况,当test任务卡死时,执行结果如下所示:

S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT    LGCC                 GCC
0.00   0.00   0.00  19.56  98.57  98.69    199    1.963   120   23.186   25.149 Metadata GC Threshold Last ditch collection
12.50   0.00   0.00  19.56  98.57  98.69    204    2.003   125   24.085   26.088 Metadata GC Threshold Metadata GC Threshold
0.00   0.00   0.00  19.56  98.57  98.69    209    2.038   130   24.982   27.020 Metadata GC Threshold Last ditch collection
0.00   0.00   0.00  19.56  98.57  98.70    215    2.079   136   26.061   28.141 Metadata GC Threshold Last ditch collection
18.75   0.00   0.00  19.56  98.57  98.70    220    2.115   141   26.951   29.066 Metadata GC Threshold Metadata GC Threshold

可以看到Old使用率(O列)较低,Metaspace使用率(M列)接近100%,FGC执行很频繁,GC的原因为“Metadata GC Threshold”或“ditch collection”。

根据以上现象可知,Gradle执行test任务卡死的原因,是Metaspace内存溢出。

使用“jstat -gc”命令查看GradleWorkerMain进程的内存各分区使用情况,当test任务卡死时,执行结果如下所示:

S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
512.0  512.0   0.0    96.0  173568.0   0.0     349696.0   66296.8   262144.0 258427.3 31832.0 31415.2    413    3.393  334    54.315   57.708
512.0  512.0   0.0    0.0   173568.0   0.0     349696.0   66298.0   262144.0 258427.3 31832.0 31415.2    420    3.467  341    55.378   58.844
512.0  512.0   0.0    0.0   173568.0   0.0     349696.0   66300.4   262144.0 258427.3 31832.0 31415.2    426    3.522  347    56.317   59.838

可以看到Metaspace容量(MC列)为256MB,Metaspace已使用(MU列)约为252.4MB。

查看操作系统内存,物理内存还有较大剩余空间。

使用“jmap -heap”命令,查看GradleWorkerMain进程的内存使用情况,MaxMetaspaceSize为256MB。

使用jconsole工具查看,当test任务卡死时,Metaspace使用情况如下所示:

1.2.3. 设置Gradle执行test任务使用新进程

参考 https://docs.gradle.org/current/userguide/java_testing.html#sec:test_execution 、 https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:forkEvery 、 https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/Test.html#setForkEvery-java.lang.Long- 。

Gradle的test任务的forkEvery参数,可指定每个测试进程执行的测试类的最大数量,当大于该值后会创建新的测试进程。

forkEvery参数的默认值为0,代表所有的测试类重用测试进程。

将forkEvery参数值设置为非0整数后,由于需要不断停止及创建测试进程,对性能会有一定的影响。

由于默认情况下Gradle会一直使用同一个Java进程执行所有的测试类,当出现上述Metaspace内存泄露问题后无法再继续执行。可将forkEvery参数设置为合理的数值,使Gradle执行一定数量的测试类后创建新的测试进程,可解决内存溢出导致测试不再继续的问题。

1.3. 解决Gradle执行test任务卡死方法总结

  • 测试类执行完毕后关闭数据源

可参考示例项目,在测试基类TestMockBase引用的TestCommonExecutionListener的afterTestMethod()方法中,当某个类的测试方法执行完毕时,执行DruidDataSource类的close()方法关闭数据源。

测试类执行完毕后关闭数据源不能解决test任务卡死的问题,可以及时释放测试程序的数据库连接,也能避免数据库服务器连接过多。

  • 调整forkEvery参数

调整Gradle test任务的forkEvery参数值,是解决Gradle执行test任务卡死的最主要方法。

forkEvery参数配置示例如下:

test {doFirst {forkEvery = 5}
}

forkEvery参数应当根据实际情况选择合适的值,若设置过小会由于频繁创建进程影响测试速度;若设置过大可能仍会出现Metaspace内存溢出问题导致测试不继续。

  • 调整测试进程堆大小

Gradle执行test任务时,GradleWorkerMain进程的最大堆大小默认为512MB,可以根据实际情况进行修改。

参考 https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:minHeapSize 、 https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/Test.html#setMinHeapSize-java.lang.String- 。

通过test任务的minHeapSize、maxHeapSize参数,可以指定GradleWorkerMain进程的最小/最大堆大小,示例如下:

test {doFirst {minHeapSize = "256m"maxHeapSize = "1g"}
}
  • 调整测试进程Metaspace大小

可以根据实际情况进行修改GradleWorkerMain进程的MetaspaceSize、MaxMetaspaceSize参数值。

参考 https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:jvmArgs 、 https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/Test.html#jvmArgs-java.lang.Iterable-。

通过test任务的jvmArgs参数,可以指定GradleWorkerMain进程的用于启动进程的JVM的额外参数(不包括系统属性和最小/最大堆大小)。指定MetaspaceSize、MaxMetaspaceSize参数值的示例如下:

test {doFirst {jvmArgs "-XX:MetaspaceSize=64m", "-XX:MaxMetaspaceSize=256m"}
}

以上参数在gradlew/gradlew.bat/gradle/gradle.bat脚本的DEFAULT_JVM_OPTS选项修改无效。

1.4. Gradle执行test任务内存溢出问题分析

1.4.1. Metaspace内存溢出示例

重复创建不会被垃圾回收的类,可以使程序出现Metaspace内存溢出。

可参考示例工程MetaspaceOOM1、MetaspaceOOM2类,如下所示:

ClassPool classPool = ClassPool.getDefault();
int i = 0;
while (true) {classPool.makeClass("test_MetaspaceOOM:" + i++).toClass();
}
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
List<Object> list = new ArrayList<>(100000);
while (true) {Constructor<?> constructor = reflectionFactory.newConstructorForSerialization(MetaspaceOOM2.class);list.add(constructor);
}

在执行时建议使用以下JVM参数:

-Xms256m -Xmx256m -XX:MetaspaceSize=20m -XX:MaxMetaspaceSize=64m -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

若需要在出现Metaspace内存溢出时自动生成heap dump文件,可添加以下参数:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=MetaSpaceOOM.hprof

执行以上代码时,会出现多次GC日志,原因为“Metadata GC Threshold”,最后会出现异常“java.lang.OutOfMemoryError: Metaspace”,并显示内存各分区的使用情况。

1.4.2. Gradle执行test任务Metaspace内存溢出问题原因

使用“jmap -dump:format=b,file=[dump文件路径] [PID]”命令,也可以在需要时手工生成Java进程的heap dump文件,如“jmap -dump:format=b,file=test.hprof 41800”。

可以使用Eclipse Memory Analyzer(MAT)分析heap dump文件,下载地址为 https://www.eclipse.org/mat/downloads.php 。

也可以使用IBM HeapAnalyzer分析heap dump文件,下载地址为 https://www.ibm.com/support/pages/ibm-heapanalyzer 。

使用MAT分析Gradle执行test任务时出现Metasapce内存溢出时GradleWorkerMain进程的heap dump文件。

查看“Leak Suspects”报告,可疑的问题包含javassist.ClassPool与org.powermock.core.classloader.javassist.JavassistMockClassLoader类,如下所示:

查看ClassLoader信息,也可以看到大量org.powermock.core.classloader.javassist.JavassistMockClassLoader实例,如下所示:

JavassistMockClassLoader类继承自MockClassLoader类,MockClassLoader类继承自ClassLoader类。MockClassLoader类API文档为 https://javadoc.io/doc/org.powermock/powermock-core/latest/org/powermock/core/classloader/MockClassLoader.html 。

可以推测Gradle执行test任务Metaspace内存溢出问题原因是测试类执行完毕后,JavassistMockClassLoader未被释放,导致对应的类无法被回收,导致内存泄露。

1.4.3. PowerMockito内存泄露问题

  • PowerMock Classloader内存泄露问题

参考以下内容,可知PowerMock存在Classloader内存泄露问题: https://github.com/powermock/powermock/issues/227 。

  • Classloader内存泄露问题分析方法

为了分析并解决Classloader内存泄露问题,可参考以下内容(及对应系列的其他内容): http://java.jiderhamn.se/2011/12/11/classloader-leaks-i-how-to-find-classloader-leaks-with-eclipse-memory-analyser-mat/ 、 http://java.jiderhamn.se/2012/01/01/classloader-leaks-ii-find-and-work-around-unwanted-references/ 。

  • 尝试根本解决Metaspace内存泄露问题

尝试不通过forkEvery参数,根本解决Metaspace内存泄露问题。尝试了一些方法均无效,例如,在@PowerMockIgnore注解中增加Spring的包,在每个测试类执行完毕时关闭Spring Context。PowerMock Classloader内存泄露问题,可能与被测试代码,及引用依赖包有关,不容易解决,最后还是通过修改forkEvery参数解决。

Java单元测试实践-24.Gradle执行test任务卡死问题解决相关推荐

  1. Java单元测试实践-23.Gradle单元测试日志、报告与JaCoCo代码覆盖率

    Java单元测试实践-00.目录(9万多字文档+700多测试示例) https://blog.csdn.net/a82514921/article/details/107969340 1. Gradl ...

  2. Java单元测试实践-21.使用Gradle执行单元测试

    Java单元测试实践-00.目录(9万多字文档+700多测试示例) https://blog.csdn.net/a82514921/article/details/107969340 1. 使用Gra ...

  3. Java单元测试实践-01.单元测试概述与示例

    Java单元测试实践-00.目录(9万多字文档+700多测试示例) https://blog.csdn.net/a82514921/article/details/107969340 1. 前言 以下 ...

  4. Java单元测试实践-25.在本地使用H2数据库进行单元测试

    Java单元测试实践-00.目录(9万多字文档+700多测试示例) https://blog.csdn.net/a82514921/article/details/107969340 1. 前言 使用 ...

  5. Java单元测试实践-08.Stub、Replace、Suppress静态方法

    Java单元测试实践-00.目录(9万多字文档+700多测试示例) https://blog.csdn.net/a82514921/article/details/107969340 1. Stub. ...

  6. Java单元测试实践-15.Stub、Replace、Suppress Spring的方法

    Java单元测试实践-00.目录(9万多字文档+700多测试示例) https://blog.csdn.net/a82514921/article/details/107969340 1. Stub. ...

  7. Java单元测试实践-09.Mockito的Stub参数条件

    Java单元测试实践-00.目录(9万多字文档+700多测试示例) https://blog.csdn.net/a82514921/article/details/107969340 1. Mocki ...

  8. Java单元测试实践-06.Mock后Stub静态方法

    Java单元测试实践-00.目录(9万多字文档+700多测试示例) https://blog.csdn.net/a82514921/article/details/107969340 1. Mock后 ...

  9. Java单元测试实践-11.Mock后Stub Spring的@Component组件

    Java单元测试实践-00.目录(9万多字文档+700多测试示例) https://blog.csdn.net/a82514921/article/details/107969340 1. Sprin ...

最新文章

  1. python基础之python中if __name__ == '__main__': 的解析
  2. html下拉列表用ul,Vue.js做select下拉列表的实例(ul-li标签仿select标签)
  3. Bootstrap组件_路径导航,标签,徽章
  4. DSSM、CNN-DSSM、LSTM-DSSM等深度学习模型在计算语义相似度上的应用+距离运算
  5. 网页获取服务器时间,通过AFNetworking获取服务器时间
  6. SQL Server 本机 Web 服务的使用方案(转载)
  7. Github分支管理范例
  8. 第一财经周刊:硅谷就是这样
  9. 电脑全能工具箱,400+工具免费用
  10. GPS数据格式解析源代码举例
  11. scjp java程序员_Sun认证Java程序员(SCJP)考试
  12. 社交网络分析-中心性指标
  13. Ubuntu返回上级目录快捷键
  14. 计算机专业论文周进展300字,论文周进展怎么写(论文周进展情况记录8篇
  15. Java原子类Atomic详解
  16. dingo php,dingo/api 使用
  17. 利用感知机实现鸢尾花分类问题
  18. 形态学-----细化
  19. 压力应力测试软件,管道强度和应力计算软件
  20. 微软认证Programming in C# 70-483 MCP 首日封(首日拿下)

热门文章

  1. (Linux命令)剪切文件
  2. Xftp:No matching outgoing encryption algorithm found问题
  3. 光流定位原理是什么??【转】
  4. 魔兽国画水墨风格图赏:美到不行
  5. 使用Java写出一个红桃心,拿走不谢!
  6. 什么是脚本,什么是脚本语言
  7. nagios监控php-fpm,Nginx平台安装Nagios监控服务
  8. JAVA把账号密码存入数据库_jdbc,采用properties文件保存数据库账号密码以及链接...
  9. Mac 安装watchman 常见错误
  10. 绿了杰克船长?深扒马斯克情感关系史…