原文链接:https://plumbr.io/outofmemoryerror/requested-array-size-exceeds-vm-limit
导语
堆溢出是应该是我们后端工程师日常经常遇到的问题,本篇用相对比较简单的过程写了一次堆溢出从测试发现到解决的过程,希望阅读本篇能对你有所帮助。
正文
以下是用于测试OOM的测试代码:
public class HeapMemUseTest {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
while(true) {
sb.append(System.currentTimeMillis());
}
}
}
这段代码非常简单,其目的就是为了模拟OOM,将其编译后,通过以下命令运行:
java -Xmx10m -Xms10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./oom.out HeapMemUseTest
其中的参数代表的意义为:
-Xmx和-Xms分别是用于指定该Java进程初使化的最小堆内存以及可以使用的最大堆内存的,这里设置为10M
-XX:+HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath参数分别用于指定发生OOM是否要导出堆以及导出堆的文件路径
该命令一执行,立即就会发生OOM,并打印如下的日志:
fenglibin@fenglibin-HP:~/eclipse_neon_workspace/Test/bin$ java -Xmx10m -Xms10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./oom.out HeapMemUseTest
java.lang.OutOfMemoryError: Java heap space
Dumping heap to ./oom.out ...
Heap dump file created [5513523 bytes in 0.027 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:700)
at java.lang.StringBuilder.append(StringBuilder.java:214)
at HeapMemUseTest.main(HeapMemUseTest.java:13)
查看当前路径,oom.out文件已经生成了,该文件就是应用在发生OOM异常时自动导出的堆文件。那我们此时需要对该文件进行分析,因为其中记录了是什么对象导出了应用程OOM的发生。
分析OOM的工具推荐使用MAT,在配置好Java环境的电脑中,直接打开即可,不需要安装,然后通过MAT打开已经生成的OOM文件oom.out,出现如下提示,选择“Leak Suspects Report”执行内存泄漏检查分析:
点击Finish按钮后,MAT会将可疑的内存泄漏的对象都展现出来:
可以看到线程java.lang.Thread @ 0xff617e80 的main方法中,有一个本地变量占用了96.43%的堆内存,实际内存占用的是char[]数组,因而被检测出来为OOM可疑的元凶。点击红色框中的“See stacktrace”,可以直接看到该对象所在线程的堆栈信息:
直接定位到了发生OOM的代码所在位置,至此该示例分析完成,MAT工具本身还有其它许多的功能,这里就不一一细说了。
导语
本篇是Java OOM的高级篇,文章从问题现象、问题排查、问题分析道原因探索都写的非常详细,很适合初中级后端工程师阅读,也很贴心的附上了排查代码,阅读完本篇相信大家对JAVA OOM的排查和解决能有质的跨越!
正文
问题现象
【告警通知-应用异常告警】
简单看下告警的信息:拒绝连接,反正就是服务有问题了,请不要太在意马赛克。
环境说明
Spring Cloud F版。
项目中默认使用 spring-cloud-sleuth-zipkin 依赖得到 zipkin-reporter。分析的版本发现是 zipkin-reporter版本是 2.7.3 。
版本:2.0.0.RELEASE
问题排查
通过告警信息,知道是哪一台服务器的哪个服务出现问题。首先登录服务器进行检查。
1、检查服务状态和验证健康检查URL是否ok
“这一步可忽略/跳过,与实际公司的的健康检查相关,不具有通用性。
查看服务的进程是否存在。
ps -ef | grep 服务名 ps -aux | grep 服务名
查看对应服务健康检查的地址是否正常,检查 ip port 是否正确
“是不是告警服务检查的url配置错了,一般这个不会出现问题
验证健康检查地址
“这个健康检查地址如:http://192.168.1.110:20606/serviceCheck 检查 IP 和 Port 是否正确。
# 服务正常返回结果
curl http://192.168.1.110:20606/serviceCheck
{"appName":"test-app","status":"UP"}
# 服务异常,服务挂掉
curl http://192.168.1.110:20606/serviceCheck
curl: (7) couldn't connect to host
2、查看服务的日志
查看服务的日志是否还在打印,是否有请求进来。查看发现服务OOM了。
tips:java.lang.OutOfMemoryError GC overhead limit exceeded
oracle官方给出了这个错误产生的原因和解决方法:Exception in thread thread_name: java.lang.OutOfMemoryError: GC Overhead limit exceeded Cause: The detail message “GC overhead limit exceeded” indicates that the garbage collector is running all the time and Java program is making very slow progress. After a garbage collection, if the Java process is spending more than approximately 98% of its time doing garbage collection and if it is recovering less than 2% of the heap and has been doing so far the last 5 (compile time constant) consecutive garbage collections, then a java.lang.OutOfMemoryError is thrown. This exception is typically thrown because the amount of live data barely fits into the Java heap having little free space for new allocations. Action: Increase the heap size. The java.lang.OutOfMemoryError exception for GC Overhead limit exceeded can be turned off with the command line flag -XX:-UseGCOverheadLimit.
原因:大概意思就是说,JVM花费了98%的时间进行垃圾回收,而只得到2%可用的内存,频繁的进行内存回收(最起码已经进行了5次连续的垃圾回收),JVM就会曝出ava.lang.OutOfMemoryError: GC overhead limit exceeded错误。
上面tips来源:java.lang.OutOfMemoryError GC overhead limit exceeded原因分析及解决方案
3、检查服务器资源占用状况
查询系统中各个进程的资源占用状况,使用 top 命令。查看出有一个进程为 11441 的进程 CPU 使用率达到300%,如下截图:
然后 查询这个进程下所有线程的CPU使用情况:
“top -H -p pid 保存文件:top -H -n 1 -p pid > /tmp/pid_top.txt
# top -H -p 11441
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
11447 test 20 0 4776m 1.6g 13m R 92.4 20.3 74:54.19 java
11444 test 20 0 4776m 1.6g 13m R 91.8 20.3 74:52.53 java
11445 test 20 0 4776m 1.6g 13m R 91.8 20.3 74:50.14 java
11446 test 20 0 4776m 1.6g 13m R 91.4 20.3 74:53.97 java
....
查看 PID:11441 下面的线程,发现有几个线程占用cpu较高。
4、保存堆栈数据
打印系统负载快照
top -b -n 2 > /tmp/top.txt
top -H -n 1 -p pid > /tmp/pid_top.txt
cpu升序打印进程对应线程列表
ps -mp-o THREAD,tid,time | sort -k2r > /tmp/进程号_threads.txt
看tcp连接数 (最好多次采样)
lsof -p 进程号 > /tmp/进程号_lsof.txt
lsof -p 进程号 > /tmp/进程号_lsof2.txt
查看线程信息 (最好多次采样)
jstack -l 进程号 > /tmp/进程号_jstack.txt
jstack -l 进程号 > /tmp/进程号_jstack2.txt
jstack -l 进程号 > /tmp/进程号_jstack3.txt
查看堆内存占用概况
jmap -heap 进程号 > /tmp/进程号_jmap_heap.txt
查看堆中对象的统计信息
jmap -histo 进程号 | head -n 100 > /tmp/进程号_jmap_histo.txt
查看GC统计信息
jstat -gcutil 进程号 > /tmp/进程号_jstat_gc.txt
生产对堆快照Heap dump
jmap -dump:format=b,file=/tmp/进程号_jmap_dump.hprof 进程号
堆的全部数据,生成的文件较大。
jmap -dump:live,format=b,file=/tmp/进程号_live_jmap_dump.hprof 进程号
dump:live,这个参数表示我们需要抓取目前在生命周期内的内存对象,也就是说GC收不走的对象,一般用这个就行。
拿到出现问题的快照数据,然后重启服务。
问题分析
根据上述的操作,已经获取了出现问题的服务的GC信息、线程堆栈、堆快照等数据。下面就进行分析,看问题到底出在哪里。
1、分析cpu占用100%的线程
转换线程ID从jstack生成的线程堆栈进程分析。
将 上面线程ID 为11447 :0x2cb711444 :0x2cb411445 :0x2cb511446 :0x2cb6转为 16进制(jstack命令输出文件记录的线程ID是16进制)。第一种转换方法 :
$ printf “0x%x” 11447
“0x2cb7”
第二种转换方法 : 在转换的结果加上 0x即可。
查找线程堆栈
$ cat 11441_jstack.txt | grep "GC task thread"
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f971401e000 nid=0x2cb4 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f9714020000 nid=0x2cb5 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007f9714022000 nid=0x2cb6 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007f9714023800 nid=0x2cb7 runnable
发现这些线程都是在做GC操作。
2、分析生成的GC文件
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 0.00 100.00 99.94 90.56 87.86 875 9.307 3223 5313.139 5322.446
S0:幸存1区当前使用比例S1:幸存2区当前使用比例E:Eden Space(伊甸园)区使用比例O:Old Gen(老年代)使用比例M:元数据区使用比例CCS:压缩使用比例YGC:年轻代垃圾回收次数FGC:老年代垃圾回收次数FGCT:老年代垃圾回收消耗时间GCT:垃圾回收消耗总时间
FGC 十分频繁。
3、分析生成的堆快照
使用 Eclipse Memory Analyzer 工具。
分析的结果:
看到堆积的大对象的具体内容:
问题大致原因,InMemoryReporterMetrics 引起的OOM。zipkin2.reporter.InMemoryReporterMetrics @ 0xc1aeaea8Shallow Size: 24 B Retained Size: 925.9 MB
也可以使用:Java内存分析进行分析,如下截图。
4、原因分析和验证
因为出现了这个问题,查看出现问题的这个服务 zipkin的配置,和其他服务没有区别。发现配置都一样。
然后看在试着对应的 zipkin 的jar包,发现出现问题的这个服务依赖的 zipkin版本较低。
有问题的服务的 zipkin-reporter-2.7.3.jar其他没有问题的服务 依赖的包 :zipkin-reporter-2.8.4.jar
将有问题的服务依赖的包版本升级,在测试环境进行验证,查看堆栈快照发现没有此问题了。
原因探索
查 zipkin-reporter的 github:搜索 相应的资料找到此 下面这个issues
修复代码和验证代码
对比两个版本代码的差异:
简单的DEMO验证:
// 修复前的代码:
private final ConcurrentHashMap
new ConcurrentHashMap
// 修复后的代码:
private final ConcurrentHashMap
new ConcurrentHashMap<>();
修复后使用 这个key :Class extends Throwable> 替换 Throwable。
简单验证:
解决方案
将zipkin-reporter 版本进行升级即可。使用下面依赖配置,引入的 zipkin-reporter版本为 2.8.4 。
小建议:配置JVM参数的时候还是加上下面参数,设置内存溢出的时候输出堆栈快照.
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=path/filename.hprof
导语
运维人员反馈一个容器化的java程序每跑一段时间就会出现OOM问题,重启后,间隔大概两天后复现。本篇就线上Docker上Springboot程序OOM问题来做一次详细的性能排查和性能优化,文章写的非常详细是一次非常优化的性能优化文章,希望能帮助到大家。
正文
问题调查
一、查日志
由于是容器化部署的程序,登上主机后使用docker logs ContainerId查看输出日志,并没有发现任何异常输出。使用docker stats查看容器使用的资源情况,分配了2G大小,目前使用率较低,也没有发现异常。
二、缺失的工具
打算进入容器内部一探究竟,先使用docker ps 找到java程序的ContainerId,再执行docker exec -it ContainerId /bin/bash进入容器。进入后,本想着使用jmap、jstack 等JVM分析命令来诊断,结果发现命令都不存在,显示如下:
bash: jstack: command not found
bash: jmap: command not found
bash: jps: command not found
bash: jstat: command not found
突然意识到,可能打镜像的时候使用的是精简版的JDK,并没有这些jVM分析工具,但是这仍然不能阻止我们分析问题的脚步,此时docker cp命令就派上用场了,它的作用是:在容器和宿主机之间拷贝文件。这里使用的思路是:拷贝一个新的jdk到容器内部,目的是为了执行JVM分析命令,参照用法如下:
Usage: docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH [flags]
有了JVM工具,我们就可以开始分析咯。
三、查GC情况
通过jstat查看gc情况
bin/jstat -gcutil 1 1s
看样子没有什么问题,full gc也少。再看一下对象的占用情况,由于是容器内部,进程号为1,执行如下命令:
bin/jmap -histo 1 |more
发现ByteBuffer对象占用最高,这是异常点一。
四、查线程快照情况
通过jstack查看线程快照情况。
bin/jstack -l 1 > thread.txt
下载快照,这里推荐一个在线的线程快照分析网站。
https://gceasy.io
上传后,发现创建的线程近2000个,且大多是TIMED_WAITING状态。感觉逐渐接近真相了。点击详情发现有大量的kafka-producer-network-thread | producer-X 线程。如果是低版本则是大量的ProducerSendThread线程。(后续验证得知),可以看出这个是kafka生产者创建的线程,如下是生产者发送模型:
据生产者的发送模型,我们知道,这个sender线程主要做两个事,一是获取kafka集群的Metadata共享给多个生产者,二是把生产者送到本地消息队列中的数据,发送至远端集群。而本地消息队列底层的数据结构就是java NIO的ByteBuffer。
这里发现了异常点二:创建过多kafka生产者。
由于没有业务代码,决定写一个Demo程序来验证这个想法,定时2秒创建一个生产者对象,发送当前时间到kafka中,为了更好的观察,启动时指定jmx端口,使用jconsole来观察线程和内存情况,代码如下:
nohup java -jar -Djava.rmi.server.hostname=ip
-Dcom.sun.management.jmxremote.port=18099
-Dcom.sun.management.jmxremote.rmi.port=18099
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false -jar
com.hyq.kafkaMultipleProducer-1.0.0.jar 2>&1 &
连接jconsole后观察,发现线程数一直增长,使用内存也在逐渐增加,具体情况如下图:
故障原因回顾分析到这里,基本确定了,应该是业务代码中循环创建Producer对象导致的。在kafka生产者发送模型中封装了 Java NIO中的 ByteBuffer 用来保存消息数据,ByteBuffer的创建是非常消耗资源的,尽管设计了BufferPool来复用,但也经不住每一条消息就创建一个buffer对象,这也就是为什么jmap显示ByteBuffer占用内存最多的原因。
总结
在日常的故障定位中,多多使用JDK自带的工具,来帮助我们辅助定位问题。一些其他的知识点:jmap -histo显示的对象含义:
[C 代表 char[]
[S 代表 short[]
[I 代表 int[]
[B 代表 byte[]
[[I 代表 int[][]
如果导出的dump文件过大,可以将MAT上传至服务器,分析完毕后,下载分析报告查看,命令为:
./mat/ParseHeapDump.sh active.dump org.eclipse.mat.api:suspects
org.eclipse.mat.api:overview org.eclipse.mat.api:top_components
可能尽快触发Full GC的几种方式:
System.gc();或者Runtime.getRuntime().gc();
jmap -histo:live或者jmap -dump:live。这个命令执行,JVM会先触发gc,然后再统计信息。
老生代内存不足的时候
Linux CPU使用率超过100%的原因
今天在服务器上部署流媒体做推流的时候使用top命令发现CPU占用率竟高达270%
在top模式下按1可看见CPU的数量是4
百度了一番发现原来top命令是按CPU总使用率来显示的,4核理论上最高可达400%
原文:https://www.cnblogs.com/duhuo/p/6065921.html
今天跑了一个非常耗时的批量插入操作。。通过top命令查看cpu以及内存的使用的时候,cpu的时候查过了120%。。以前没注意。。通过在top的情况下按大键盘的1,查看的cpu的核数为4核。
通过网上查找,发现top命令显示的是你的程序占用的cpu的总数,也就是说如果你是4核cpu那么cpu最高占用率可达400%,top里显示的是把所有使用率加起来。
这里我们也可以查看一下CPU信息:在命令行里输入:cat /proc/cpuinfo
这里可以看到cpu cores : 4
转自:https://blog.csdn.net/guotao15285007494/article/details/84135713
https://www.cnblogs.com/duhuo/p/6065921.html