一应用在进行稳定性测试(线程池+无限循环),临下班看了一眼,发现进程关闭了查看log,发现堆内存溢出Error导致JVM进程崩溃




java leak怎么判断是内存泄漏 java内存泄露怎么查_JVM


于是重启进程,通过top观察,内存确实在稳定增长,数量级应该已经可以暴露是什么对象泄漏导致了。


java leak怎么判断是内存泄漏 java内存泄露怎么查_JVM_02


一、通过jinfo查看JVM启动信息


# 查看内存占用,rsz为实际内存,单位kb
# ps -eo 'pid,rsz,vsz' | grep pid
ps -eo 'pid,rsz,vsz' | grep 27403

PID   RSZ    VSZ
27403 492484 3052832

# 查看系统信息
# jinfo -sysprops pid

# 查看JVM信息
# jinfo -flags pid

jinfo -flags 27403
Attaching to process ID 27403, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.25-b02
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=62914560 -XX:MaxHeapSize=1006632960 -XX:MaxNewSize=335544320 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=20971520 -XX:OldSize=41943040 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops
Command line:


二、通过jmap查看堆内存


# jmap -heap pid
jmap -heap 27403

Attaching to process ID 27403, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.25-b02

using thread-local object allocation.
Mark Sweep Compact GC

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 1006632960 (960.0MB)
   NewSize                  = 20971520 (20.0MB)
   MaxNewSize               = 335544320 (320.0MB)
   OldSize                  = 41943040 (40.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 216989696 (206.9375MB)
   used     = 116865984 (111.45208740234375MB)
   free     = 100123712 (95.48541259765625MB)
   53.857849545077016% used
Eden Space:
   capacity = 192937984 (184.0MB)
   used     = 116865984 (111.45208740234375MB)
   free     = 76072000 (72.54791259765625MB)
   60.57178663170856% used
From Space:
   capacity = 24051712 (22.9375MB)
   used     = 0 (0.0MB)
   free     = 24051712 (22.9375MB)
   0.0% used
To Space:
   capacity = 24051712 (22.9375MB)
   used     = 0 (0.0MB)
   free     = 24051712 (22.9375MB)
   0.0% used
tenured generation:
   capacity = 481370112 (459.0703125MB)
   used     = 288820968 (275.4411392211914MB)
   free     = 192549144 (183.6291732788086MB)
   59.99977165179711% used

9569 interned Strings occupying 839416 bytes.


基本上进程占用的都是老年代的空间,如果观察jstat,应该能看出来,固定的内存在S0、S1反复移动后进入老年代,之后频繁的Full GC也无法清除泄漏的内存。

三、dump内存,使用jvisualvm分析


# dump内存进行分析
#jmap -dump:format=b,file=file.dump pid

jmap -dump:format=b,file=27403.dump 27403


将dump文件导入jvisualvm,可以看到大量字符串对象占用了空间;逐步展开至实例,可以看到测试报告中的字符串,基本可以定位所在。


java leak怎么判断是内存泄漏 java内存泄露怎么查_java 内存泄漏_03


java leak怎么判断是内存泄漏 java内存泄露怎么查_JVM_04


由于是将单元测试代码提取修改为多线程,死循环执行,测试稳定性;但是并未关闭测试报告。并且将logback和Reporter.log进行了绑定,但是循环不结束,Reporter持续在写入,导致一直持有写入对象,导致物理内存被消耗殆尽,进而引起OOM。

四、修改程序

其实稳定性测试无需报告的,将此功能关闭即可,但为了验证,还是简单处理一下。


java leak怎么判断是内存泄漏 java内存泄露怎么查_java 内存泄漏_05


经过一晚上执行,早上程序依然在稳定运行。


ps -eo 'pid,rsz,vsz' | grep 4095
PID   RSZ    VSZ
4095 161620 3051960


相较内存溢出时的占用已经下降了许多。

五、关于内存使用的建议

1、尽早释放无用对象的引用,让引用变量在退出活动域后自动设置为null。

2、程序进行字符串处理时,尽量避免使用String,而使用StringBuffer。

3、静态变量是全局的,GC不会回收。

4、避免集中创建对象尤其是大对象,如果可以的话尽量使用流操作。

JVM会突然需要大量内存,这时会触发GC优化系统内存环境。


m_totalBytes = m_request.getContentLength();
m_binArray = new byte[m_totalBytes];
m_totalBytes这个变量特别大,导致数组分配很大的内存空间,而且该数组不能及时释放。


5、尽量运用对象池技术以提高系统性能。

生命周期长的对象拥有生命周期短的对象时容易引发内存泄漏,例如大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理,然后解决一块释放一块的策略。

6、不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。

可以适当的使用hash创建一组对象容器,然后从容器中去获取对象,而不用每次new之后又丢弃。

7、优化配置。


参考:

https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/memleaks004.html#CIHCDBJB

https://docs.oracle.com/javase/6/docs/technotes/tools/share/jvisualvm.html