java内存溢出怎么定位?如何解决java内存溢出?

在java项目开发的过程中,我们肯定会遇到java内存溢出,一旦出现java内存溢出这样的情况就会导致程序无法正常运行,因此我们学会进行java内存溢出定位是很有必要的,下面我们就给大家讲解一下java内存溢出定位的方法。

首先按照CPU问题的定位思路进行定位,对 Java 调用堆栈进行分析:

使用top -c 查看 CPU 占用高的进程:

java内存溢出怎么定位?如何解决java内存溢出?.png

从 top 命令的结果看,19272 号进程 CPU 占用率最高,基本确定问题是该进程引起,可以从 Command 栏看到这正是算法模块程序,注意图是线下4C机器上复现时的截图。使用ps -mp pid -o THREAD,tid,time命令定位问题线程。

ps -mp 19272 -o THREAD,tid,time
USER %CPU PRI SCNT WCHAN USER SYSTEM TID TIME
USER 191 - - - - - - 00:36:54
USER 0.0 19 - futex_ - - 19272 00:00:00
USER 68.8 19 - futex_ - - 19273 00:13:18
USER 30.2 19 - - - - 19274 00:05:50
USER 30.2 19 - - - - 19275 00:05:50
USER 30.2 19 - - - - 19276 00:05:50
USER 30.1 19 - - - - 19277 00:05:49
USER 0.4 19 - futex_ - - 19278 00:00:05
USER 0.0 19 - futex_ - - 19279 00:00:00
USER 0.0 19 - futex_ - - 19280 00:00:00
USER 0.0 19 - futex_ - - 19281 00:00:00
USER 0.4 19 - futex_ - - 19282 00:00:04
USER 0.3 19 - futex_ - - 19283 00:00:03
USER 0.0 19 - futex_ - - 19284 00:00:00
USER 0.0 19 - futex_ - - 19285 00:00:00
USER 0.0 19 - futex_ - - 19286 00:00:00
USER 0.0 19 - skb_wa - - 19362 00:00:00

从结果可以看到,出现问题的线程主要是 19273-19277。使用jstack查看出现问题的线程堆栈信息。由于 jstack 使用的线程号是十六进制,因此需要先把线程号从十进制转换为十六进制。

$ printf "%x\n"
19273
4 b49
$ jstack 12262 | grep - A 15 4 b49 "main"
#1 prio= 5 os_prio = 0 tid = 0x00007f98c404c000 nid = 0x4b49 runnable[0x00007f98cbc58000]
java.lang.Thread.State: RUNNABLE
at java.util.ArrayList.iterator(ArrayList.java: 840)
at optional.score.MultiSkuDcAssignmentEasyScoreCalculator.updateSolution(MultiSkuDcAssignmentEasyScoreCalculator.java: 794)
at optional.score.MultiSkuDcAssignmentEasyScoreCalculator.calculateScore(MultiSkuDcAssignmentEasyScoreCalculator.java: 80)
at optional.score.MultiSkuDcAssignmentEasyScoreCalculator.calculateScore(MultiSkuDcAssignmentEasyScoreCalculator.java: 17)
at org.optaplanner.core.impl.score.director.easy.EasyScoreDirector.calculateScore(EasyScoreDirector.java: 60)
at org.optaplanner.core.impl.score.director.AbstractScoreDirector.doAndProcessMove(AbstractScoreDirector.java: 188)
at org.optaplanner.core.impl.localsearch.decider.LocalSearchDecider.doMove(LocalSearchDecider.java: 132)
at org.optaplanner.core.impl.localsearch.decider.LocalSearchDecider.decideNextStep(LocalSearchDecider.java: 116)
at org.optaplanner.core.impl.localsearch.DefaultLocalSearchPhase.solve(DefaultLocalSearchPhase.java: 70)
at org.optaplanner.core.impl.solver.AbstractSolver.runPhases(AbstractSolver.java: 88)
at org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java: 191)
at app.DistributionCenterAssignmentApp.main(DistributionCenterAssignmentApp.java: 61)
"VM Thread"
os_prio = 0 tid = 0x00007f98c419d000 nid = 0x4b4e runnable "GC task thread#0 (ParallelGC)"
os_prio = 0 tid = 0x00007f98c405e800 nid = 0x4b4a runnable "GC task thread#1 (ParallelGC)"
os_prio = 0 tid = 0x00007f98c4060800 nid = 0x4b4b runnable "GC task thread#2 (ParallelGC)"
os_prio = 0 tid = 0x00007f98c4062800 nid = 0x4b4c runnable "GC task thread#3 (ParallelGC)"
os_prio = 0 tid = 0x00007f98c4064000 nid = 0x4b4d runnable "VM Periodic Task Thread"
os_prio = 0 tid = 0x00007f98c4240800 nid = 0x4b56 waiting on condition

可以看到,除了 0x4b49 线程是正常工作线程,其它都是 gc 线程。

此时怀疑:是频繁 GC 导致的 CPU 被占满。

我们可以使用 jstat 命令查看 GC 统计:

$

jstat - gcutil 19272 2000 10
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 0.00 22.71 100.00 97.16 91.53 2122 19.406 282 809.282 828.688
0.00 0.00 100.00 100.00 97.16 91.53 2122 19.406 283 809.282 828.688
0.00 0.00 92.46 100.00 97.16 91.53 2122 19.406 283 812.730 832.135
0.00 0.00 100.00 100.00 97.16 91.53 2122 19.406 284 812.730 832.135
0.00 0.00 100.00 100.00 97.16 91.53 2122 19.406 285 815.965 835.371
0.00 0.00 100.00 100.00 97.16 91.53 2122 19.406 285 815.965 835.371
0.00 0.00 100.00 100.00 97.16 91.53 2122 19.406 286 819.492 838.898
0.00 0.00 100.00 100.00 97.16 91.53 2122 19.406 286 819.492 838.898
0.00 0.00 100.00 100.00 97.16 91.53 2122 19.406 287 822.751 842.157
0.00 0.00 30.78 100.00 97.16 91.53 2122 19.406 287 825.835 845.240

重点关注一下几列:

YGC:年轻代垃圾回收次数

YGCT:年轻代垃圾回收消耗时间

FGC:老年代垃圾回收次数

FGCT:老年代垃圾回收消耗时间

GCT:垃圾回收消耗总时间

可以看到,20s 的时间中进行了 5 次 full GC,仅仅耗费在 GC 的时间已经到了 17s。

增加启动参数,展示详细 GC 过程。通过增加 jvm 参数,更快暴露 GC 问题,并展示 GC 详细过程

java -Xmx1024m 
-verbose:gc。
[Full GC (Ergonomics) 1046527K->705881K(1047552K), 1.8974837 secs]
[Full GC (Ergonomics) 1046527K->706191K(1047552K), 2.5837756 secs]
[Full GC (Ergonomics) 1046527K->706506K(1047552K), 2.6142270 secs]
[Full GC (Ergonomics) 1046527K->706821K(1047552K), 1.9044987 secs]
[Full GC (Ergonomics) 1046527K->707130K(1047552K), 2.0856625 secs]
[Full GC (Ergonomics) 1046527K->707440K(1047552K), 2.6273944 secs]
[Full GC (Ergonomics) 1046527K->707755K(1047552K), 2.5668877 secs]
[Full GC (Ergonomics) 1046527K->708068K(1047552K), 2.6924427 secs]
[Full GC (Ergonomics) 1046527K->708384K(1047552K), 3.1084132 secs]
[Full GC (Ergonomics) 1046527K->708693K(1047552K), 1.9424100 secs]
[Full GC (Ergonomics) 1046527K->709007K(1047552K), 1.9996261 secs]
[Full GC (Ergonomics) 1046527K->709314K(1047552K), 2.4190958 secs]
[Full GC (Ergonomics) 1046527K->709628K(1047552K), 2.8139132 secs]
[Full GC (Ergonomics) 1046527K->709945K(1047552K), 3.0484079 secs]
[Full GC (Ergonomics) 1046527K->710258K(1047552K), 2.6983539 secs]
[Full GC (Ergonomics) 1046527K->710571K(1047552K), 2.1663274 secs]

至此基本可以确定,CPU 高负载的根本原因是内存不足导致频繁 GC。

如何解决java内存溢出?

引起内存溢出的原因有很多种,列举一下常见的有以下几种:

1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;

2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;

3.代码中存在死循环或循环产生过多重复的对象实体;

4.使用的第三方软件中的BUG;

5.启动参数内存值设定的过小

内存溢出的解决方案:

第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)

第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。

第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。

重点排查以下几点:

1.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。

2.检查代码中是否有死循环或递归调用。

3.检查是否有大循环重复产生新对象实体。

4.检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。

第四步,使用内存查看工具动态查看内存使用情况

一旦有java内存溢出的情况就会影响后续开发工作,所以java人员学会排查原因很重要,只有就知道了原因,才能“对症下药”!最后大家如果想要了解更多java常见问答知识,敬请关注奇Q工具网。

推荐阅读:

qt怎么生成exe文件?qt如何自定义信号函数?

git客户端怎么下载?git如何进行配置?

程序员面试自我介绍怎么说?程序员面试要注意什么?