一次Java服务内存过高的分析过程
现象
年前,收到了短信报警,显示A服务的某台机器内存过高,超过80%
如上图所示,内存会阶段性增加。奇怪的是,十多台机器中只有这一台有这个问题
堆内内存分析
最先怀疑是内存泄漏的问题,所以首先使用jmap命令把堆dump下来
jmap -dump:format=b,file=service.hprof 1948
用MAT分析堆文件发现了一个奇怪的问题,下载下来7G的文件MAT显示的Size只有700M
后来知道不加live选项会把堆中所有的对象dump下来,即使是已经是垃圾的对象
参考 what are "live" objects in java heap? (heap dump with jmap)
live 选项会在讲堆内容dump到文件时,强制做一次fullGC,剩下的就是live对象,也就是从GC Root可以寻达的对象
为什么有时不能用live呢,因为fullGC可能会让应用卡主,不能接受这种情况适用不增加live选项
后来我重新使用了live选项dump下来有900M
jmap -dump:live,file=live-dump.bin <pid>
MAT的内存泄漏报告
首先用MAT的Leak Suspect看一下
看到了org.springframework.boot.loader.LaunchedURLClassLoader
这个对象有240M
因为一直不太清楚live选项的原因,所以就想用其他工具看看这7G到底都是什么
IDEA自带工具分析
把hprof拖入IDEA,就可以看到上图,上面分析了所有的对象,从占用的大小就可以看出来
其中的Shallow表示的是:
对象本身占用内存的大小,也就是对象头加成员变量(不是成员变量的值)的总和
Retained表示的是:
如果一个对象被释放掉,那会因为该对象的释放而减少引用进而被释放的所有的对象(包括被递归释放的)所占用的heap大小,即对象被垃圾回收器回收后能被GC从内存中移除的所有对象之和。
具体参考一文让你理解什么是shallow heap及retained heap
我们把前几名的Shallow加起来也有好几G了
也可以点进Object[]查看这700多万的对象数组都是什么
可以看到一个占用450M的Object[10240]属于LaunchedURLClassLoader
上图中可以看到这450M中TagCateSimilarityUtils占了200M, 这是一个本地缓存,虽然占的多了一点,通过比对正常服务器的堆转储,是没有问题的
查看int[]对象时,发现了很多Retained是0的对象,也就是说这些对象已经是不可达对象,只不过还没有被回收,那是不是就是只要让这些对象回收了,内存占用就下来了
解决垃圾回收问题
上面分析中看到了很多对象没有被回收,怀疑是没有FullGC(Mixed)所以老年代中的垃圾对象,一直都没回收,看了一下JVM参数
-Xmx8g -Xms8g -Xmn4g -Xss1024K -XX:PermSize=256m -XX:MaxPermSize=512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=32m -Dfastjson.parser.safeMode=true
调整为,删除了一些默认和不生效的参数,移除了Xmn,交给G1自己调整
-Xmx8g -Xms8g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=32m -Dfastjson.parser.safeMode=true
不过并没有解决问题,此时我发现了新的问题:
- 按理GC完只有不到1G的对象,为什么监控中会显示内存很高呢
- 设置了Xms=8g,按理一启动就会占用至少8G内存,监控为什么是从6G开始增长的
这个问题和虚拟内存和实际内存有关,可以参考linux top命令 实存(RES)与虚存(VIRT)详解
VIRT:
1、进程“需要的”虚拟内存大小,包括进程使用的库、代码、数据,以及malloc、new分配的堆空间和分配的栈空间等;
2、假如进程新申请10MB的内存,但实际只使用了1MB,那么它会增长10MB,而不是实际的1MB使用量。
RES:
1、进程当前使用的内存大小
2. 如果申请10MB的内存,实际使用1MB,它只增长1MB,与VIRT相反;
监控显示的内存是实际占用的内存,Xms设置的是虚拟内存,参考降低 Java 程序的“虚拟内存地址”占用
可以看出 Java Heap 与 Metaspace 紧挨着分配,两块一共占用了 3GB 的 Size(虚拟内存地址空间),而表征物理内存占用的 Rss 却只有 673288KB。也就是说,mmap 只是给进程分配一个线性区域(虚拟内存),并没有分配物理内存,只有当进程访问这块内存时,操作系统才会分配具体的内存页给进程,这就是 Linux 内存管理的延迟分配策略
内存一旦被分配给Java程序,就不会还给操作系统了,所以监控看起来内存就是只增不减,个人理解这样对于Java程序也是有好处的,不用频繁申请内存,对于垃圾的区域标记然后就可以重新写了
Java也存在JVM参数-XX:+AlwaysPreTouch 来实现启动就把堆内存全都申请到
为了抵消延迟分配策略,在进程启动时强制分配好 Java Heap 的物理内存,虽然增加了启动延时,但是可以减少进程运行时由于分配内存造成的延时
总结
堆内内存是正常的,不存在内存泄漏,只是正常的内存使用增长
堆外内存分析
在搜索LaunchedURLClassLoader内存泄漏问题时,看到了Spring Boot引起的“堆外内存泄漏”排查及经验总结,学到了如何进行堆外内存分析
一次完整的JVM堆外内存泄漏java故障排查记录也是一篇值得学习的如何分析堆外内存的文章
不过我们问题产生的原因和上面的不一致
使用pmap分析应用的内存占用,但不太容易看出是什么占用了内存
pmap -x 1927 | sort -k3 -r -n
JVM的NativeMemoryTracking参数会看到更详细,不过需要重启,而且会有5%-10%的性能损耗
// 写在启动参数上面
-XX:NativeMemoryTracking=detail
// 生成内容在下图中
jcmd 1927 VM.native_memory detail scale=MB > temp3.txt
经过对堆外内存的观察,发现确实有一些比较高,例如线程占700M,发现了有很多不必要的线程,但不是随着时间不断增加的
上图中的Total一行的commited=9788MB,表示Java应用程序堆内加堆外内存最大可达这么多,这么一算12 * 0.8 = 9.6G,确实超了报警阈值
这样看来,堆外内存也没有问题,是Xmx和内存阈值设置的不匹配,导致内存正常使用的情况下报警了,也是没有合理预估堆外内存占用的原因,堆外占了快2G的内存
将报警阈值调到85%,发现内存在周期性的增长和减少,并不会超过阈值,如下图
总结
服务的内存使用情况是正常的,无论是堆内还是堆外内存,需要做的是设置合理的堆内存大小,预估堆外内存大小,合理设置报警阈值
通过这次分析也学习了如何对Java程序内存进行分析,也学习了很多工具
也意思到监控工具的重要性,在看别人分析的过程中,一般都会对JVM资源进行监控,这样就能很明显看出资源的动态,因为我们目前没有这样的工具,所以分析起来就比较麻烦
附
前面提到其中只有一台机器有这个现象,还没有找到原因,不过有几个现象:
- 内存增长快
- 部分日志大小比其他机器大
- 耗时每隔一段时间会增加
对比日志发现是Redis超时比较多,所以怀疑可能是机器网络的问题,不过目前还无结论
参考
[1] 一文让你理解什么是shallow heap及retained heap
[2] linux top命令 实存(RES)与虚存(VIRT)详解
[3] 降低 Java 程序的“虚拟内存地址”占用
[4] 一次完整的JVM堆外内存泄漏java故障排查记录
[5] Spring Boot引起的“堆外内存泄漏”排查及经验总结
一次Java服务内存过高的分析过程的更多相关文章
- Java服务器内存过高&CPU过高问题排查
一.内存过高 1.内存过高一般有两种情况:内存溢出和内存泄漏 (1)内存溢出:程序分配的内存超出物理机的内存大小,导致无法继续分配内存,出现OOM报错 (2)内存泄漏:不再使用的对象一直占据着内存不释 ...
- 性能分析 | Java服务器内存过高&CPU过高问题排查
一.内存过高 1.内存过高一般有两种情况:内存溢出和内存泄漏 (1)内存溢出:程序分配的内存超出物理机的内存大小,导致无法继续分配内存,出现OOM报错 (2)内存泄漏:不再使用的对象一直占据着内存不释 ...
- 一个驱动导致的内存泄漏问题的分析过程(meminfo->pmap->slabtop->alloc_calls)
关键词:sqllite.meminfo.slabinfo.alloc_calls.nand.SUnreclaim等等. 下面记录一个由于驱动导致的内存泄漏问题分析过程. 首先介绍问题背景,在一款嵌入式 ...
- JAVA服务cpu占用高排查
最近线上机器偶尔有台cpu达到100%,还居高不下.同样负载的其他机器却正常,我想肯定是代码哪里有问题了 首先我们top看下 可定位到对应占用高的PID 然后=>ps -mp PID -o TH ...
- Java服务CPU占用高问题定位方法
1. 概述 提供一种简单的方法来定位CPU高的问题. 找到CPU高的进程,比如232543: 执行top -H -p pid,找到占用CPU最高的线程号,比如232544,转换成16进制38c60: ...
- java线程数过高原因分析
作者:鹿丸不会多项式 出处:http://www.cnblogs.com/hechao123 转载请先与我联系. 一.问题描述 前阵子我们因为B机房故障,将所有的流量切到了A机房,在经历了推送+ ...
- Mysql占用内存过高的优化过程
一.环境说明: 操作系统:CentOS 6.5 x86_64 数据库:Mysql 5.6.22 服务器:阿里云VPS,32G Mem,0 swap 二.问题情况: 1.某日发现公司线上系统的Mysql ...
- cpu 或 内存 偏高的分析套路
参考资料: https://mp.weixin.qq.com/s/fb9YxJr-yDdYQ86RE47y1w 1)通过针对此软件专业的分析工具或命令,找到占用cpu高的函数,2)通过调用栈(或源码搜 ...
- 记一次Mysql占用内存过高的优化过程
一.环境说明: 操作系统:CentOS 6.5 x86_64 数据库:Mysql 5.6.22 服务器:阿里云VPS,32G Mem,0 swap 二.问题情况: 1.某日发现公司线上系统的Mysql ...
- linux下分析java程序占用CPU、内存过高
一.CPU过高分析 1)使用TOP命令查看CPU.内存使用状态可以发现CPU占用主要分为两部分,一部分为系统内核空间占用CPU百分比,一部分为用户空间占用CPU百分比.其中CPU状态中标示id的为空闲 ...
随机推荐
- go实现发送邮件验证码
目录 开启SMTP服务: 发邮件测试 业务实现 开启SMTP服务: QQ邮箱参考下面连接: QQ邮箱如何开通SMTP服务 https://jingyan.baidu.com/article/00a07 ...
- 2020年9月至10月 Splashtop 新功能
Splashtop 已为 Splashtop Business Access.Splashtop Remote Support.Splashtop SOS 和 Splashtop On-Prem ...
- WPF使用Shape实现复杂线条动画
看到巧用 CSS/SVG 实现复杂线条光效动画的文章,便也想尝试用WPF的Shape配合动画实现同样的效果.ChokCoco大佬的文章中介绍了基于SVG的线条动画效果和通过角向渐变配合 MASK 实现 ...
- Angular的管道
Angular的管道可以看作成是一个数据格式化展示的工具.管道可以将数据格式化显示,而不改变源数据.获取数据可能简单到创建一个局部变量就行,也可能复杂到从WebSocket中获取数据流.一旦取到数据, ...
- 聊聊MySQL是如何处理排序的
本文分享自华为云社区<MySQL怎样处理排序️如何优化需要排序的查询?>,作者:菜菜的后端私房菜. 前言 在MySQL的查询中常常会用到 order by 和 group by 这两个关键 ...
- (二)Redis 数据类型与结构
1.值的数据类型 Redis "快"取决于两方面,一方面,它是内存数据库,另一方面,则是高效的数据结构.Redis 键值对中值的数据类型,也就是数据的保存形式有5种:String( ...
- centos安装kvm博客
一 centos如何安装kvm https://blog.csdn.net/yulsh/article/details/91790804
- WPF显示网络图片的几种方法
1.利用数据流 1 Image img; 2 byte[] btyarray = GetImageFromResponse(imageUrl); 3 4 //字节数据转流 5 MemoryStream ...
- 微软官方开源免费的Blazor UI组件库 - Fluent UI Blazor
前言 今天大姚给大家分享一个由微软官方开源(MIT License).免费的Blazor UI组件库:Fluent UI Blazor. 全面的ASP.NET Core Blazor简介和快速入门 F ...
- numpy基础--利用数组进行数据处理
以下代码的前提:import numpy as np numpy数组可以将许多种数据处理任务表述为简洁的数组表达式,用数组表达式替换循环的做法,通常被称为矢量化. 官方说明文档:Array creat ...