java 内存泄露-堆内存溢出Error导致JVM进程崩溃,如何处理内存?
一应用在进行稳定性测试(线程池+无限循环),临下班看了一眼,发现进程关闭了查看log,发现堆内存溢出Error导致JVM进程崩溃。
于是重启进程,通过top观察,内存确实在稳定增长,数量级应该已经可以暴露是什么对象泄漏导致了。
一、通过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内存java 内存泄露,使用jvisualvm分析
# dump内存进行分析
#jmap -dump:format=b,file=file.dump pid
jmap -dump:format=b,file=27403.dump 27403
将dump文件导入jvisualvm,可以看到大量字符串对象占用了空间;逐步展开至实例,可以看到测试报告中的字符串,基本可以定位所在。
由于是将单元测试代码提取修改为多线程,死循环执行,测试稳定性;但是并未关闭测试报告。并且将logback和Reporter.log进行了绑定,但是循环不结束java 内存泄露,Reporter持续在写入,导致一直持有写入对象,导致物理内存被消耗殆尽,进而引起OOM。
四、修改程序
其实稳定性测试无需报告的,将此功能关闭即可,但为了验证,还是简单处理一下。
经过一晚上执行,早上程序依然在稳定运行。
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、优化配置。
参考:
#CIHCDBJB