当前位置:网站首页>一次内存泄露排查小结
一次内存泄露排查小结
2022-08-03 11:57:00 【freesOcean】
问题重现
前段时间给DSP(实际竞价广告投放系统)系统开发了一个前置数据去重处理服务,开始两天没有问题,但是第三天去看数据,发现进程不在。
查看阿里云机器负载,发现前两天内存持续在高位,初步怀疑内存溢出,导致异常退出。
排查思路
1.确定代码中有大量对象占用的地方,着重排查:因为堆内存是JVM中内存占用最大的一块。
2.多线程代码排查:多线程下容易出现隐蔽的bug
3.是否是机器本身内存太小,有其他进程占用大量内存。
关于思路1:要清楚使用的jdk版本,以及相应版本对应的JVM内存的分布情况。
要清楚以下几点:
首先JVM内存受限于实际的最大物理内存,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体到JVM操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系 统下为2G-3G),而64bit以上的处理器就不会有限制了。
JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指定,
默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;
空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、 -Xmx相等以避免在每次GC后调整堆的大小。
JDK8后,方法区在元空间实现,元空间使用直接内存. 但是一般方法区占用内存并不会太大。
优化方法
如果进程已经挂掉,可以分析堆内存快照,具体设置方式参考下文。如果进程还在,可以查看当前的内存情况,排查问题:
通过java自带的工具查看堆内存情况
#查看堆内存使用情况 jmap -heap pid Heap Usage: PS Young Generation Eden Space: capacity = 290455552 (277.0MB) used = 290455552 (277.0MB) free = 0 (0.0MB) 100.0% used From Space: capacity = 20447232 (19.5MB) used = 14548992 (13.875MB) free = 5898240 (5.625MB) 71.15384615384616% used To Space: capacity = 20447232 (19.5MB) used = 0 (0.0MB) free = 20447232 (19.5MB) 0.0% used PS Old Generation capacity = 420478976 (401.0MB) used = 364028688 (347.16481018066406MB) free = 56450288 (53.83518981933594MB) 86.5747656310883% used
这里需要了解JVM垃圾回收器的分代逻辑,简单来说就是新创建的对象放在Eden Space, 之后将存活的对象放入From Space和To Space区域,如果超过一定次数,则放入Old Generation,所以如果老年代如果空间一直上升,而没有下降,则说明有内存泄露,导致对象一直没有释放,就要重点排查。具体可以结合dump文件。
通过java自带工具jmap查看class实例等信息,作用类似于查看dump文件。
#输出当前class的实例数目、内存占用、类全名信息。 [root@iZ0xicvgqrspr6w1sxojjeZ tmp]# jmap -histo:live 6104 | head -n 20 num #instances #bytes class name ---------------------------------------------- 1: 66065 10737048 [C 2: 12932 9308496 [B 3: 12049 4969152 [I 4: 28079 2562408 [Ljava.lang.Object; 5: 65697 1576728 java.lang.String 6: 48265 1544480 java.util.concurrent.ConcurrentHashMap$Node 7: 39782 1273024 java.util.HashMap$Node 8: 11210 1239576 java.lang.Class 9: 1036 1064000 [Lio.netty.util.Recycler$DefaultHandle; 10: 15295 611800 java.security.cert.TrustAnchor 11: 5817 511896 java.lang.reflect.Method 12: 1602 493712 [Ljava.util.concurrent.ConcurrentHashMap$Node; 13: 3865 485632 [Ljava.util.HashMap$Node; 14: 20168 484032 java.util.ArrayList 15: 27529 440464 java.lang.Object 16: 8681 347240 java.util.LinkedHashMap$Entry 17: 390 255840 io.netty.util.internal.shaded.org.jctools.queues.MpscArrayQueue
通过jstat命令查看gc情况
如果FGC次数比较频繁,则说明内存吃紧,要排查是内存本身较小,还是内存泄露。
[[email protected] work]# jstat -gc 28686 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 8192.0 7680.0 4544.0 0.0 770560.0 497923.9 1310720.0 852555.0 65624.0 62895.9 8024.0 7467.0 146565 2120.377 132 84.347 2204.724
S0C:年轻代中第一个幸存区的大小
S1C:年轻代中第二个幸存区的大小
S0U:年轻代中第一个幸存区的使用大小
S1U:年轻代中第二个幸存区的使用大小
EC:年轻代中伊甸园区的大小
EU:年轻代中伊甸园区的使用大小
OC:老年代大小
OU:老年代使用大小
MC:方法区大小
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC:年轻代gc次数
YGCT:年轻代消耗时间
FGC:老年代gc次数
FGCT:老年代gc消耗时间
GCT:gc消耗总时间
查看内存占用靠前的java进程
top -o %MEM -b -n 1 | grep java | awk '{print "PID: "$1" \t MEM: "$6" \t %CPU: "$9"% \t %MEM: "$10"%"}'
到处堆内存快照
jmap -histo [进程id] > jmap.txt
首先调整java运行时的相关参数,包括内存溢出时的堆内存快照。
nohup java -Xmx2048m -Xms2048m -Xmn768m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof -jar ${ jar_name} --spring.profiles.active=prod-us --server.port=8089 >/dev/null 2>&1 &
JVM参数 描述 默认 推荐 -Xms Java堆内存的最小值,即初始值 OS内存1/64 OS内存一半 -Xmx java堆内存最大值 OS内存1/4 OS内存一半 -Xmn 堆内存新生代大小,扣除新生代
就是老年代大小默认堆的1/3 sun推荐3/8 -Xss 每个线程的栈内存大小 和jdk有关 512k~1M 如果是本身内存设置过小,一般通过调整参数便可以解决。如果是其他问题,可以通过heapdump.hprof文件协助查看占用内存较大的对象分布,方便排查。
根据这些信息,重点排查相关部位的代码,一般都能解决。之前我还遇到在使用线程池时,采用了Executors工具类去创建线程池,而用它创建线程池,默认的等待队列是无界队列,所以有可能导致创建大量等待线程,导致内存溢出(线程要占用资源)。正确的做法是使用下面这种方式,给一个有界队列作为参数。
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2000), new DefaultThreadFactoryA(), new ThreadPoolExecutor.CallerRunsPolicy());
查看java进程常用命令:
#1.简洁信息:可以查看进程号 jps #2.可查看进程号和jar包名称 jcmd jps -lv #3.详细信息 ps -ef|grep java
查看系统内存情况:
#Top命令 ,可以查看系统负载情况,内存占用,和进程的情况 top - 18:59:23 up 5 days, 8:28, 2 users, load average: 28.50, 30.68, 30.82 Tasks: 84 total, 1 running, 83 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.3 us, 0.3 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 8008928 total, 3534996 free, 3400088 used, 1073844 buff/cache KiB Swap: 0 total, 0 free, 0 used. 4355048 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1150 root 10 -10 140104 18748 11464 S 1.3 0.2 90:48.01 AliYunDun 947 root 20 0 1354476 21268 8416 S 0.3 0.3 29:38.53 /usr/local/clou 1 root 20 0 43584 3976 2620 S 0.0 0.0 0:04.72 systemd 2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd 4 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/0:0H 5 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kworker/u4:0 6 root 20 0 0 0 0 S 0.0 0.0 0:35.24 ksoftirqd/0 7 root rt 0 0 0 0 S 0.0 0.0 0:00.44 migration/0 8 root 20 0 0 0 0 S 0.0 0.0 0:00.00 rcu_bh 9 root 20 0 0 0 0 S 0.0 0.0 1:27.46 rcu_sched
只想查看java进程的情况:
[[email protected] ~]# top -o %MEM -b -n 1 | grep java | awk '{print "PID: "$1" \t MEM: "$6" \t %CPU: "$9"% \t %MEM: "$10"%"}' PID: 1592 MEM: 2.4g %CPU: 0.0% %MEM: 31.4% PID: 1506 MEM: 383856 %CPU: 0.0% %MEM: 4.8%
知识小结
内存泄露、JVM调优等问题一般比较隐蔽,首先要对jvm的相关知识有一个大体认知,其次要熟悉java中相关工具类的正确用法,最后就是要有给力的工具,比如jprofiler等等。
边栏推荐
猜你喜欢
基于英雄联盟的知识图谱问答系统
肝完Alibaba这份面试通关宝典,我成功拿下今年第15个Offer
【MySQL功法】第5话 · SQL单表查询
LeetCode-48. 旋转图像
【MySQL功法】第2话 · 数据库与数据表的基本操作
Matlab学习12-图像处理之图像增强
赛灵思MPSOC裸机下的 USB调试实验
"Digital Economy Panorama White Paper" Financial Digital User Chapter released!
本周四晚19:00知识赋能第4期直播丨OpenHarmony智能家居项目之设备控制实现
fastposter v2.9.0 programmer must-have poster generator
随机推荐
LeetCode——622.设计循环队列
net start mysql 启动报错:发生系统错误5。拒绝访问。
解决oracle安装在linux中jdk的冲突
CDH6.3.2开启kerberos认证
Redis发布订阅和数据类型
码率vs.分辨率,哪一个更重要?
FR9811S6 SOT-23-6 23V, 2A Synchronous Step-Down DC/DC Converter
QGIS绘制演习区域示意图
Generate interface documentation online
[错题]电路维修
通过组策略安装软件和删除用户配置文件
肝完Alibaba这份面试通关宝典,我成功拿下今年第15个Offer
第四课 标识符、关键字、变量、变量的分类和作用域、常量
从零开始Blazor Server(6)--基于策略的权限验证
ROS中编译通过但是遇到可执行文件找不到的问题
FE主导打造一个运营活动平台
在线生成接口文档
flink流批一体有啥条件,数据源是从mysql批量分片读取,为啥设置成批量模式就不行
什么是bin文件?「建议收藏」
【一起学Rust】Rust的Hello Rust详细解析