背景:8G物理内存,8核CPU,jvm使用的G1垃圾回收器。

问题:线上内存占用告警,内存占用超过85%,且现象一直持续。

分析

看一下jvm启动参数配置:

-Xms6144m
-Xmx6144m
-Xss256k
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+DisableExplicitGC
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/tmp/heapdump.hprof
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/tmp/gc.log
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=10
-XX:+UseStringDeduplication
-XX:GCLogFileSize=16M
-Djava.awt.headless=true
-Djava.net.preferIPv4Stack=true
-Duser.timezone=Asia/Shanghai
-Dfile.encoding=UTF-8

进容器内,观察内存占用。

top
free -h

发现java进程占用了7G多的内存,但内存占用大小波动很小。第一想法是否有内存泄漏(回过头看,想法是好的:内存占用高怀疑是否有内存泄漏。但其实已经走了误区,内存占用稳定应当去定位java进程占用的内存被谁占用了。堆内存、元空间还是堆外内存?堆内的话是年轻代还是老年代?但还是记录一下这次jvm问题定位排查的过程),去查看堆内存的实际内存占用。

先看一下java进程pid、jmap命令路径:

ps -ef | grep java
find / -name jmap

然后,执行jmap命令查看堆内存概览:

堆信息概览:
jmap -heap pid
堆中对象概览:
jmap -histo pid | head -50

例如我实际执行的:

/data/services/jdk8u161/bin/jmap -heap 376
/data/services/jdk8u161/bin/jmap -histo 376 | head -50

堆内存分配了6G,实际占用800M,而java进程占用了7G,其它内存被谁占用了?首先想到的是堆外内存,是否是堆外内存的溢出导致的?

元空间的观察

jvm GC信息,间隔一秒打印一次:
jstat -gc pid 1000
或者
jstat -gcutil pid 1000
S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   0.0   96256.0  0.0   96256.0 1644544.0 1105920.0 4550656.0   138468.4  114280.0 109400.0 13688.0 12775.8     14    1.211   0      0.000    1.2110.0   96256.0  0.0   96256.0 1644544.0 1105920.0 4550656.0   138468.4  114280.0 109400.0 13688.0 12775.8     14    1.211   0      0.000    1.211
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   0.00 100.00  89.54   3.04  95.73  93.34     14    1.211     0    0.000    1.2110.00 100.00  89.66   3.04  95.73  93.34     14    1.211     0    0.000    1.211

MC(元空间分配):114280.0K,MU(元空间使用):109400.0K。元空间使用了100M多一点,排除。但为防止元空间的内存溢出,JVM启动参数中建议添加参数:-XX:MaxMetaspaceSize=256M

堆外内存的观察

为了观察java进程堆外内存的占用,JVM启动参数中添加参数:-XX:NativeMemoryTracking=summary,这个参数对jvm可能会有5%左右的性能损耗,所以生产环境不推荐开启。

同时,-XX:+DisableExplicitGC:禁止显示GC,即代码中声明的  System.gc();//建议jvm进行gc  不再生效。在jdk源码中使用nio申请堆外内存时,堆外内存不足时会执行 System.gc() 进行堆外内存的回收,所以,堆外内存使用较多时不推荐配置 -XX:+DisableExplicitGC

最后,为了防止堆外内存的溢出,jvm启动参数建议添加:-XX:MaxDirectMemorySize=1024M

修改后的jvm启动参数配置如下:

-Xms6144m
-Xmx6144m
-Xss256k
-XX:MetaspaceSize=256M
-XX:MaxMetaspaceSize=256M
-XX:MaxDirectMemorySize=1024M
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/tmp/heapdump.hprof
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/tmp/gc-%t.log
-Duser.timezone=Asia/Shanghai
-Dfile.encoding=UTF-8

jcmd 追踪java本地内存

配置好后,重新启动容器,jcmd 追踪java进程本地内存:

查看java进程内存占用详细情况(-XX:NativeMemoryTracking=summary,关闭NMT命令:jcmd pid VM.native_memory shutdown):
jcmd pid VM.native_memory scale=MB保存java进程内存占用情况的基准版本:
jcmd pid VM.native_memory scale=MB baseline与基准版本进行比较(若怀疑存在内存泄漏,可过段时间再执行观察):
jcmd pid VM.native_memory scale=MB summary.diff

我实际执行的命令:

/data/services/jdk8u161/bin/jcmd 376 VM.native_memory scale=MB/data/services/jdk8u161/bin/jcmd 376 VM.native_memory scale=MB baseline/data/services/jdk8u161/bin/jcmd 376 VM.native_memory scale=MB summary.diff

对比2张图,可发现内存波动很小。Java Heap(堆内存)分配了6G,实际也占用了6G。堆外内存实际占用了800多M,主要被Class(元空间)、Thread(java线程栈,含GC本地线程)、Code(本地字节码,即JIT存储热点代码地方)、GC(JVM GC额外占用的,例如G1中的Remembered Set等数据结构)、Internal(Direct Buffer直接内存,例如nio)等占用。Native Memory Tracking表示该功能自身占用的部分。

一段时间后,再次对比发现内存波动还是很小,这时候才反应过来,是不是不存在内存泄漏,只不过是java进程真的占用了这么高的内存。那为什么java进程内存占用一直这么高呢?仔细看一下上图,发现这一行: Java Heap (reserved=6144MB, committed=6144MB) ,JVM为java堆保留了6G,堆实际也占用了6G,才回想起来JVM的启动参数配置中:-Xms6144m -Xmx6144m,自己设置了最大堆为6G,再加上堆外内存的800多M,那java进程实际不就是占用7G左右嘛。

解决

修改-Xms6144m -Xmx6144m为-Xms4096m -Xmx4096m,物理内存的一半,重启后,容器的内存占用最后稳定在60%左右,内存占用过高的问题没有再次出现。最后的JVM启动参数配置为:

-Xms4096m
-Xmx4096m
-Xss256k
-XX:MetaspaceSize=256M
-XX:MaxMetaspaceSize=256M
-XX:MaxDirectMemorySize=1024M
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/tmp/heapdump.hprof
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/tmp/gc-%t.log
-Duser.timezone=Asia/Shanghai
-Dfile.encoding=UTF-8

思考

为何jvm堆实际占用稳定在6G?我尝试手动进行了full GC,马上再去观察,发现堆内存实际占用还是6G。可是jmap -heap pid观察堆信息概览,看到heap used实际只有几十M,那这是为什么呢?为什么JVM没有将内存归还给操作系统呢?可参考博客:https://segmentfault.com/a/1190000040050819

总结:JVM 的垃圾回收,只是一个逻辑上的回收,回收的只是 JVM 申请的那一块逻辑堆区域,将数据标记为空闲之类的操作,不是调用 free 将内存归还给操作系统。

JVM 还是会归还内存给操作系统的,只是因为这个代价比较大,所以不会轻易进行。而且不同垃圾回收器的内存分配算法不同,归还内存的代价也不同。或者说归还内存给操作系统的操作并没有那么简单,执行起来代价过高,JVM 自然不会在每次 GC 后都进行内存的归还。

JVM问题定位使用到的命令

top
free -h
ps -ef | grep java
find / -name jmap
堆信息概览:
jmap -heap pid
堆中对象概览:
jmap -histo pid | head -50
jvm GC信息:
jstat -gc pid 1000
jstat -gcutil pid 1000
查看java进程内存占用详细情况(-XX:NativeMemoryTracking=summary,关闭NMT命令:jcmd pid VM.native_memory shutdown):
jcmd pid VM.native_memory scale=MBFull GC后显示存活对象:
jmap -histo:live pid | head -20
查看jvm启动参数:
jinfo -flags pid
or
jps -v
Full GC后dump堆栈文件:
jmap -dump:format=b,live,file=heapdump.hprof pid保存java进程内存占用情况的基准版本:
jcmd pid VM.native_memory scale=MB baseline
与基准版本进行比较(若怀疑存在内存泄漏,可过段时间再执行观察):
jcmd pid VM.native_memory scale=MB summary.diff

一次jvm导致线上内存占用过高问题定位相关推荐

  1. Java 线上cpu占用过高分析

    转载于:https://blog.csdn.net/ch999999999999999999/article/details/113151519 感谢作者:ch999999999999999999 J ...

  2. 【jvm内存占用过高分析】

    [jvm内存占用过高分析] 1.首先进入服务容器内 //获取服务容器名称 kubectl get pods |grep <服务名称>// 进入容器内部 kubectl -it exec & ...

  3. android线上内存监控_如何在Android上监控(和减少)您的数据使用情况

    android线上内存监控 Increasingly sophisticated phones and data-hungry applications make it easier than eve ...

  4. JVM:线上服务CPU爆满,如何排查(三)

    0. 引言 前一段时间出现了一个正则表达式引起的线上CPU爆满的问题,一开始没有在第一时间定位到问题,这里也特此记录一下,同时也系统的梳理下CPU爆满问题的排查思路和方法,为后续的同学提供参考. 1. ...

  5. 内存占用过高,缓存不释放导致死机处理方案

    故障现象: 1.某分行部署的某台服务器内存占用过高,导致死机: 2.代码层面检查暂未发现问题,服务器硬重启持续一段时间后(3-5天)再次占满. 发现问题: 赶往现场后进行检查,当时是一切正常的,今有D ...

  6. 一个神奇的bug:OOM?优雅终止线程?系统内存占用较高?

    摘要:该项目是DAYU平台的数据开发(DLF),数据开发中一个重要的功能就是ETL(数据清洗).ETL由源端到目的端,中间的业务逻辑一般由用户自己编写的SQL模板实现,velocity是其中涉及的一种 ...

  7. Spring cloud开发内存占用过高解决方法

    https://blog.csdn.net/wanhuiguizong/article/details/79289986 版权声明:本文为博主原创文章,转载请声明文章来源和原文链接. https:// ...

  8. Shared UI Mesh内存占用过高

    1)Shared UI Mesh 内存占用过高 ​2)GPU Skinning有办法实现阴影的做法吗 3)在真机上特效不跟着Spine运动 4)复制AnimatorController无效问题 5)P ...

  9. 运维:你们 JAVA 服务内存占用太高,还只增不减!告警了,快来接锅

    先点赞再看,养成好习惯 某天,运维老哥突然找我:"你们的某 JAVA 服务内存占用太高,告警了!GC 后也没释放,内存只增不减,是不是内存泄漏了!" 然后我赶紧看了下监控,一切正常 ...

最新文章

  1. Linux的完全免费特性
  2. [Swift]LeetCode793. 阶乘函数后K个零 | Preimage Size of Factorial Zeroes Function
  3. spring配置详解-属性注入(p名称空间SPEL表达式)
  4. 华为鸿蒙系统不卡,华为鸿蒙系统,到底能不能取代安卓?网友:细节决定成败...
  5. C语言,使用union了解内存
  6. 反恐精英起源服务器文件在哪,反恐精英:起源人物模型放哪里
  7. H5项目常见问题汇总及解决方案
  8. 2020 CSDN 博客之星投票已开启,请大家为我投票,多谢啦
  9. Python+BI可视化分析2000W数据之后,告诉你这届毕业生有多难
  10. Pandas——concat(合并)
  11. 应用数据挖掘进行客户关系管理
  12. autocad插件无法加载无法运行的解决办法
  13. 含泪整理最优质草坪灯光域网素材,你想要的这里都有
  14. MacBook安装jdk8
  15. 信号峰峰值Vpp与功率和dbm的换算
  16. 索尼录音笔怎么导出录音内容_搜狗AI走向产业改造:纵横捭阖术与录音笔的新声态...
  17. shopNC开发手册
  18. 两天赚 2 千,用 Python 接私活,真香!
  19. 音乐欣赏之古典乐曲经典开头
  20. sql server 2008 导入导出数据大全

热门文章

  1. 50款水滴效果PS笔刷
  2. Mask R-CNN 原理解析
  3. OGR遍历要素及重置
  4. 快衰落、慢衰落,平坦衰落、频率选择性衰落
  5. CTF(二)DES中的S盒
  6. 基于Java的飞机雷电射击游戏的设计实现(Eclipse开发)
  7. php中seq是什么意思,seq-answer
  8. MarkDown基本语法使用教程
  9. 蓝桥六届 打印大X JAVA
  10. 如何使用CorelDRAW 2019绘制谷歌浏览器Logo