性能调优-------(六)内存使用率高,5分钟如何快速排查问题(真实场景实战图文讲解)

零、总结:

本次问题通过分析,由于平时70%+的内存使用率,目前达到88%是由于5个月系统未重新发布内存数据和缓存不断增加以及堆内存的增加累计达到了内存使用率的报警阀值88%。
在这里插入图片描述

那么平时如果出现内存使用率偏高的问题,应该如何解决呢?下面的几个步骤其实就是从硬件-》系统-》进程来由大到小解决的。
1、由于内存分配问题(也就是我这里的问题,对应解决办法如步骤1和2),


2019-1-21 19:57:00 更新
在深入理解java虚拟机(第二版)周志明的第5章中有高性能硬件上的程序部署策略,有一些指引方向,当时我排查问题还没看该书。特此在这里补充下。


2、长期持有super big对象耗内存(对应解决办法如步骤3)
3、死锁问题(对应解决办法如步骤4)
4、其他原因比如poll长连接或者其他导致并发线程增多的原因(对应解决办法如步骤5和6)
5、定位某个进程的内存什么问题(如步骤7)
6、线程具体什么代码或者什么原因导致的(如步骤8)
对于jvm8+调优点击下面
参考实战和指导手册

如果你是小白码农,还没有到达码工的层级,那么可以按照如下的教程定位问题。如果依然有疑问可以关注公众号【小诚信驿站】或者加 QQ群300458205

一、知识点了解:

了解下硬件和系统和进程之间的关系。
在这里插入图片描述
1.1硬件:
top执行命令可以得到

Cpu(s):  0.0%us,  0.3%sy,  0.0%ni, 99.7%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:     73728k total,    70048k used,     3680k free,        0k buffers
Swap:    16384k total,     4696k used,    11688k free,    64716k cached
top看到的内存使用情况,有一部分是缓存,mem那行后面有个buffers ,swap那行后面有个cached,这两个就是缓存大小。所以如果要计算应用程序真正使用物理内存的情况,应该是used-cached-buffers才对,所以刚才top看到的物理内存使用情况为70048k-64716k=5332k。

如果想要直接看内存使用情况可以执行 free命令 后面加个m是以M为单位显示

free -m
-----------
              total       used       free     shared    buffers     cached
Mem:            72         69          2          0          0         63
-/+ buffers/cache:          5         66
Swap:           16          4         11
其中第一行用全局角度描述系统使用的内存状况: 
total——总物理内存 
used——已使用内存,一般情况这个值会比较大,因为这个值包括了cache+应用程序使用的内存 
free——完全未被使用的内存 
shared——应用程序共享内存 多个进程之间共享的内存部分,比如公共库libc.so等
buffers——缓存,主要用于目录方面,inode值等(ls大目录可看到这个值增加)缓存将要放到硬盘里的数据 
cached——缓存,缓存从硬盘读出来的数据用于已打开的文件:
当你读写文件的时候,Linux内核为了提高读写性能与速度,会将文件在内存中进行缓存,这部分内存就是Cache Memory(缓存内存)。即使你的程序运行结束后,Cache Memory也不会自动释放。这就会导致你在Linux系统中程序频繁读写文件后,你会发现可用物理内存会很少。 
其实这缓存内存(Cache Memory)在你需要使用内存的时候会自动释放,所以你不必担心没有内存可用。 
只有当 free 减去 cached 剩下的这部分内存情况紧张时,才有可能出现应用程序没有足够内存使用的情况 
注意-/+ buffers/cache: 5 66这行。 
前个值表示-buffers/cache—–>不包括缓存,应用程序物理内存使用情况,即 -buffers/cache=used-buffers-cached ,所以此时应用程序才用了5M内存 。 
后个值表示+buffers/cache—–>所有可供应用程序使用的内存大小,free加上缓存值,即+buffers/cache=free+buffers+cached ,所以此时还有接近66M 内存可供程序使用。 

swap:
交换分区、交互内存:

交互分区属于硬盘空间,做为内存不足时的临时内存使用
swap 主要的功能是当实体内存不够时,则某些在内存当中所占的程序会暂时被移动到 swap 当中,让实体内存可以被需要的程序来使用。另外,如果你的主机支持电源管理模式, 也就是说,你的 Linux 主机系统可以进入“休眠”模式的话,那么, 运行当中的程序状态则会被纪录到 swap 去,以作为“唤醒”主机的状态依据! 另外,有某些程序在运行时,本来就会利用 swap 的特性来存放一些数据段, 所以, swap 来是需要创建的!只是不需要太大!

1.2系统:

虚拟内存是操作系统内核为了对进程地址空间进行管理(process address space management)而精心设计的一个逻辑意义上的内存空间概念。我们程序中的指针其实都是这个虚拟内存空间中的地址。比如我们在写完一段C++程序之后都需要采用g++进行编译,这时候编译器采用的地址其实就是虚拟内存空间的地址。因为这时候程序还没有运行,何谈物理内存空间地址?凡是程序运行过程中可能需要用到的指令或者数据都必须在虚拟内存空间中。既然说虚拟内存是一个逻辑意义上(假象的)的内存空间,为了能够让程序在物理机器上运行,那么必须有一套机制可以让这些假象的虚拟内存空间映射到物理内存空间(实实在在的RAM内存条上的空间)。这其实就是操作系统中页映射表(page table)所做的事情了。内核会为系统中每一个进程维护一份相互独立的页映射表。页映射表的基本原理是将程序运行过程中需要访问的一段虚拟内存空间通过页映射表映射到一段物理内存空间上,这样CPU访问对应虚拟内存地址的时候就可以通过这种查找页映射表的机制访问物理内存上的某个对应的地址。“页(page)”是虚拟内存空间向物理内存空间映射的基本单元。

1.3进程
拿java举例
在这里插入图片描述

VSS- Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
RSS- Resident Set Size 实际使用物理内存(包含共享库占用的内存)
PSS- Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
USS- Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)
一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS

二、问题:

内存使用率88%高于80%报警。

三、原因:

指标含义:内存使用率百分比(%)。
指标解释:容器的内存使用率是读取物理机cgroup下面的文件的,获取的是整个容器的内存使用率并不是针对某个程序。物理机内存使用率和使用free命令计算结果是一致的。物理机和容器两者内存计算数据是独立的
计算公式近似等于为:进程使用的(物理内存和本地内存和共享内存)、未被换出的物理内存大小,单位kb。RES=CODE+DATA

四、解决步骤:

用top中查看RES是操作系统角度看jvm的内存占用。
用jmap查看的堆内存,是用jvm的角度看jvm内部程序的内存占用。
存在差异是因为jvm有一些共享库和共享内存,被操作系统计入RES中,但未被jvm计入

1、查看哪些应用占用内存比较大

查看哪几个进程内存占用最高:top -c,输入大写M,以内存使用率从高到低排序

在这里插入图片描述

PID : 进程id
PPID : 父进程id
RUSER : Real user name
UID : 进程所有者的用户id
USER : 进程所有者的用户名
GROUP : 进程所有者的组名
TTY : 启动进程的终端名。不是从终端启动的进程则显示为 ?
PR : 优先级
NI : nice值。负值表示高优先级,正值表示低优先级
P : 最后使用的CPU,仅在多CPU环境下有意义
%CPU : 上次更新到现在的CPU时间占用百分比
TIME : 进程使用的CPU时间总计,单位秒
TIME+ : 进程使用的CPU时间总计,单位1/100秒
%MEM : 进程使用的物理内存百分比
VIRT : 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES
SWAP : 进程使用的虚拟内存中,被换出的大小,单位kb。
RES : 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA
CODE : 可执行代码占用的物理内存大小,单位kb
DATA : 可执行代码以外的部分(数据段+栈)占用的物理内存大小,单位kb
SHR : 共享内存大小,单位kb
nFLT : 页面错误次数
nDRT : 最后一次写入到现在,被修改过的页面数。
S : 进程状态。D=不可中断的睡眠状态 R=运行 S=睡眠 T=跟踪/停止 Z=僵尸进程
COMMAND : 命令名/命令行
WCHAN : 若该进程在睡眠,则显示睡眠中的系统函数名
Flags : 任务标志,参考 sched.h

默认情况下仅显示比较重要的 PID、USER、PR、NI、VIRT、RES、SHR、S、%CPU、%MEM、TIME+、COMMAND 列。可以通过下面的快捷键来更改显示内容。 更改显示内容
通过 f 键可以选择显示的内容。按 f 键之后会显示列的列表,按 a-z 即可显示或隐藏对应的列,最后按回车键确定。
按 o 键可以改变列的显示顺序。按小写的 a-z 可以将相应的列向右移动,而大写的 A-Z 可以将相应的列向左移动。最后按回车键确定。
按大写的 F 或 O 键,然后按 a-z 可以将进程按照相应的列进行排序。而大写的 R 键可以将当前的排序倒转

2、通过jmap -heap 进程id 命令排除是由于堆分配内存问题。得到如下结果

Attaching to process ID 542287, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.20-b23

using thread-local object allocation.
Garbage-First (G1) GC with 43 thread(s)
//堆配置信息
Heap Configuration:
   //指定 jvm heap 在使用率小于 n 的情况下 ,heap 进行收缩 ,Xmx==Xms 的情况下无效 , 如 
   MinHeapFreeRatio         = 40
   //指定 jvm heap 在使用率大于 n 的情况下 ,heap 进行扩张 ,Xmx==Xms 的情况下无效 , 如 
   MaxHeapFreeRatio         = 70
   //最大堆空间
   MaxHeapSize              = 5393874944 (5144.0MB)
   //设置Yong Generation的初始值大小,一般情况下,不允许-XX:Newratio值小于1,即Old要比Yong大。
   NewSize                  = 1363144 (1.2999954223632812MB)
   //设置Yong Generation的最大值大小
   MaxNewSize               = 3235905536 (3086.0MB)
   OldSize                  = 5452592 (5.1999969482421875MB)
   //设置年轻代和老年代的比例,默认情况下,此选项为2
   NewRatio                 = 2
   //默认eden空间大小和survivor空间大小的比,默认情况下为8
   SurvivorRatio            = 8
   //初始化元空间大小,控制gc阀值,gc后动态增加或者降低元空间大小,默认情况下平台的不同,步长为12-20M
   MetaspaceSize            = 209715200 (200.0MB)
   //默认1G,这个参数主要是设置Klass Metaspace的大小,不过这个参数设置了也不一定起作用,前提是能开启压缩指针,假如-Xmx超过了32G,压缩指针是开启不来的。如果有Klass Metaspace,那这块内存是和Heap连着的。
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   //为类元数据分配的最大空间量
   MaxMetaspaceSize         = 536870912 (512.0MB)
   //堆内存中一个Region的大小可以通过-XX:G1HeapRegionSize参数指定,大小区间只能是1M、2M、4M、8M、16M和32M,总之是2的幂次方,如果G1HeapRegionSize为默认值,则在堆初始化时计算Region的实践大小
   G1HeapRegionSize         = 2097152 (2.0MB)
//堆的使用信息
Heap Usage:
G1 Heap:
//区域数量
   regions  = 2572
//堆内存大小
   capacity = 5393874944 (5144.0MB)
//已经使用了
   used     = 3216639400 (3067.62638092041MB)
//空闲着的堆内存
   free     = 2177235544 (2076.37361907959MB)
   59.63503850933923% used
以下同理
G1 Young Generation:
Eden Space:
   regions  = 425
   capacity = 2650800128 (2528.0MB)
   used     = 891289600 (850.0MB)
   free     = 1759510528 (1678.0MB)
   33.62341772151899% used
Survivor Space:
   regions  = 1
   capacity = 2097152 (2.0MB)
   used     = 2097152 (2.0MB)
   free     = 0 (0.0MB)
   100.0% used
G1 Old Generation:
   regions  = 1109
   capacity = 2740977664 (2614.0MB)
   used     = 2323252648 (2215.62638092041MB)
   free     = 417725016 (398.37361907958984MB)
   84.75999927009985% used

35394 interned Strings occupying 3871104 bytes.

截止到这里,本次问题已经找到了。因为设置的堆空间分配额比较大。将近63% =5g/8g。内存使用率计算公式为code+Data。本地内存和共享内存和可执行代码以外的部分(数据段+栈)等,当堆内存还没有达到full gc的时候,内存使用率问题就显现出来了。将内存分配最大值设为4g.并重新更新配置文件,发布应用
在这里插入图片描述
在这里插入图片描述
但是这里存在一个问题,内存使用率高,刚才提到的一个情况就是堆内存接近最大值不会进行fullgc么?fullgc不就帮你回收堆空间了么?
这是个好的问题。
实际上他确实发生fullgc了,我们可以查到
在这里插入图片描述
在这里插入图片描述
那么为什么没有解决内存使用率问题呢?而是将堆分配额重新调整之后,内存使用率才降下去。
简单点说,就是如果你的项目需要人手10个人,你跟领导要了10个人,当项目只是第一个迭代干完了,那么你会不会立马将其中的5个人交给领导?答案是不会的,但是如果现在重新分配,领导说我就给你5个人下一个迭代你先干着,这样你就需要必须上交5个人。具体的点这里。详细的说明内存是如何管理的。


如果上面也没有问题定位到原因,则继续按照步骤排查

3、找到最耗内存的对象
jmap -histo 进程ID(带上:live则表示先进行一次FGC再统计,如jmap -histo:live 进程ID)

在这里插入图片描述
可以看到上面最大的实例进程 将近30M。

4、导出内存转储快照dump文件:
4.1、通过java进程命令定位 系统进程并使用jmap工具dump文件。

ps -ef | grep java 
生成dump文件的命令:
jmap -dump:format=b,file=20181218.dump 16048
file后面的是自定义的文件名,最后的数字是进程的pid。

4.2、使用jvisualvm来分析dump文件:

jvisualvm是JDK自带的Java性能分析工具,在JDK的bin目录下,文件名就叫jvisualvm.exe。
jvisualvm可以监控本地、远程的java进程,实时查看进程的cpu、堆、线程等参数,对java进程生成dump文件,并对dump文件进行分析。
假设我现在下载下来的是txt文件也可以直接扔给jvisualvm来分析。

在这里插入图片描述

4.3、使用方式:直接双击打开jvisualvm.exe,点击文件->装入,在文件类型那一栏选择堆,选择要分析的dump文件,打开。

导入文件以后界面如下图:
在这里插入图片描述
在这里插入图片描述

可以看到,dump文件里记录的堆中的实例,总大小大概5392M左右,(用第一行的实例大小除以百分比就能算出来)

4.4、现在看堆转储的线程问题

在这里插入图片描述
每一个部分的含义如下:
“http-nio-1601-Acceptor-0” 线程名称
daemon 线程的类型
prio=5 线程的优先级别
tid=290 线程ID
RUNNABLE 线程当前的状态

4.5、线程当前的状态是我们主要关注的内容。
dump文件中描述的线程状态

runnable:运行中状态,在虚拟机内部执行,可能已经获取到了锁,可以观察是否有locked字样。
blocked:被阻塞并等待锁的释放。
wating:处于等待状态,等待特定的操作被唤醒,一般停留在park(), wait(), sleep(),join() 等语句里。
time_wating:有时限的等待另一个线程的特定操作。
terminated:线程已经退出

4.6、进程的区域划分

进入区(Entry Set):等待获取对象锁,一旦对象锁释放,立即参与竞争。
拥有区(The Owner):已经获取到锁。
等待区(Wait Set):表示线程通过wait方法释放了对象锁,并在等待区等待被唤醒。

4.7、方法调用修饰

locked: 成功获取锁
waiting to lock:还未获取到锁,在进入去等待;
waiting on:获取到锁之后,又释放锁,在等待区等待;

4.8、OQL(对象查询语言)
如果需要根据某些条件来过滤或查询堆的对象,比如现在我们查询下系统中类加载器一共有几种?

在这里插入图片描述

4.9、引导计数
引导类 (即 JVM 在未使用任何 java.lang.ClassLoader 实例的情况下加载的 Java 平台类) 的计数
其余展示的与名称一样

5、统计进程打开的句柄数:ls /proc/进程ID/fd |wc -l

6、统计进程打开的线程数:ls /proc/进程ID/task |wc -l

7、使用jstat查看进程的内存使用情况
jstat [Options] vmid [interval] [count]
Options,选项,我们一般使用 -gcutil 查看gc情况
vmid,VM的进程号,即当前运行的java进程号
interval,间隔时间,单位为秒或者毫秒
count,打印次数,如果缺省则打印无数次

  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00 100.00  32.20   7.05  48.98  95.35 102490 10125.674     1   39.100 10164.775
  0.00 100.00  32.57   7.05  48.98  95.35 102490 10125.674     1   39.100 10164.775
  0.00 100.00  32.94   7.05  48.98  95.35 102490 10125.674     1   39.100 10164.775
  0.00 100.00  33.31   7.05  48.98  95.35 102490 10125.674     1   39.100 10164.775
  0.00 100.00  33.62   7.05  48.98  95.35 102490 10125.674     1   39.100 10164.775

 S0C:年轻代中第一个survivor(幸存区)的容量 (字节) 
S1C:年轻代中第二个survivor(幸存区)的容量 (字节) 
S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节) 
S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节) 
EC:年轻代中Eden(伊甸园)的容量 (字节) 
EU:年轻代中Eden(伊甸园)目前已使用空间 (字节) 
OC:Old代的容量 (字节) 
OU:Old代目前已使用空间 (字节) 
PC:Perm(持久代)的容量 (字节) 
PU:Perm(持久代)目前已使用空间 (字节) 
YGC:从应用程序启动到采样时年轻代中gc次数 
YGCT:从应用程序启动到采样时年轻代中gc所用时间(s) 
FGC:从应用程序启动到采样时old代(全gc)gc次数 
FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s) 
GCT:从应用程序启动到采样时gc用的总时间(s) 
NGCMN:年轻代(young)中初始化(最小)的大小 (字节) 
NGCMX:年轻代(young)的最大容量 (字节) 
NGC:年轻代(young)中当前的容量 (字节) 
OGCMN:old代中初始化(最小)的大小 (字节) 
OGCMX:old代的最大容量 (字节) 
OGC:old代当前新生成的容量 (字节) 
PGCMN:perm代中初始化(最小)的大小 (字节) 
PGCMX:perm代的最大容量 (字节) 
PGC:perm代当前新生成的容量 (字节) 
S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比 
S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比 
E:年轻代中Eden(伊甸园)已使用的占当前容量百分比 
O:old代已使用的占当前容量百分比 
P:perm代已使用的占当前容量百分比 
M:元空间中已使用的占当前容量百分比
S0CMX:年轻代中第一个survivor(幸存区)的最大容量 (字节) 
S1CMX :年轻代中第二个survivor(幸存区)的最大容量 (字节) 
ECMX:年轻代中Eden(伊甸园)的最大容量 (字节) 
DSS:当前需要survivor(幸存区)的容量 (字节)(Eden区已满) 
TT: 持有次数限制 
MTT : 最大持有次数限制

8、.用jstack查看一下
jstack pid | grep tid(线程ID) -A 30

在这里插入图片描述

  • 7
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小诚信驿站

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值