因而赶快登录探测服务器,首先是 top free df
三连,结果还真发现了些异常。html
咱们的探测进程 CPU 占用率特别高,达到了 900%。java
咱们的 Java 进程,并不作大量 CPU 运算,正常状况下,CPU 应该在 100~200% 之间,出现这种 CPU 飙升的状况,要么走到了死循环,要么就是在作大量的 GC。shell
使用 jstat -gc pid [interval]
命令查看了 java 进程的 GC 状态,果真,FULL GC 达到了每秒一次。bash
这么多的 FULL GC,应该是内存泄漏没跑了,因而 使用 jstack pid > jstack.log
保存了线程栈的现场,服务器
使用 jmap -dump:format=b,file=heap.log pid
保存了堆现场,而后重启服务,报警邮件终于中止了。多线程
jstat 是一个很是强大的 JVM 监控工具,通常用法是: jstat [-options] pid interval
app
它支持的查看项有:jvm
使用它,对定位 JVM 的内存问题颇有帮助。工具
栈的分析很简单,看一下线程数是否是过多,多数栈都在干吗。spa
> grep 'java.lang.Thread.State' jstack.log | wc -l > 464
才四百多线程,并没有异常。
> grep -A 1 'java.lang.Thread.State' jstack.log | grep -v 'java.lang.Thread.State' | sort | uniq -c |sort -n 10 at java.lang.Class.forName0(Native Method) 10 at java.lang.Object.wait(Native Method) 16 at java.lang.ClassLoader.loadClass(ClassLoader.java:404) 44 at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method) 344 at sun.misc.Unsafe.park(Native Method)
线程状态好像也无异常,接下来分析堆文件。
堆文件都是一些二进制数据,在命令行查看很是麻烦,Java 为咱们提供的工具都是可视化的,Linux 服务器上又无法查看,那么首先要把文件下载到本地。
因为咱们设置的堆内存为 4G,因此 dump 出来的堆文件也很大,下载它确实很是费事,不过咱们能够先对它进行一次压缩。
gzip
是个功能很强大的压缩命令,特别是咱们能够设置 -1 ~ -9
来指定它的压缩级别,数据越大压缩比率越大,耗时也就越长,推荐使用 -6~7, -9 实在是太慢了,且收益不大,有这个压缩的时间,多出来的文件也下载好了。
MAT 是分析 Java 堆内存的利器,使用它打开咱们的堆文件(将文件后缀改成 .hprof
), 它会提示咱们要分析的种类,对于此次分析,果断选择 memory leak suspect
。
从上面的饼图中能够看出,绝大多数堆内存都被同一个内存占用了,再查看堆内存详情,向上层追溯,很快就发现了罪魁祸首。
找到内存泄漏的对象了,在项目里全局搜索对象名,它是一个 Bean 对象,而后定位到它的一个类型为 Map 的属性。
这个 Map 根据类型用 ArrayList 存储了每次探测接口响应的结果,每次探测完都塞到 ArrayList 里去分析,因为 Bean 对象不会被回收,这个属性又没有清除逻辑,因此在服务十来天没有上线重启的状况下,这个 Map 愈来愈大,直至将内存占满。
内存满了以后,没法再给 HTTP 响应结果分配内存了,因此一直卡在 readLine 那。而咱们那个大量 I/O 的接口报警次数特别多,估计跟响应太大须要更多内存有关。
给代码 owner 提了 PR,问题圆满解决。
出处:https://www.cnblogs.com/zhenbianshu/p/10305428.html