排查springboot内存占用过高问题
所需命令:
ps命令:Linux命令。强大的进程状态监控命令。可以查看进程以及进程中线程的当前CPU使用情况。属于当前状态的采样数据。
top命令:Linux命令。可以查看实时的CPU使用情况。也可以查看最近一段时间的CPU使用情况。
这两个命令详情可参考:https://blog.csdn.net/XiXavier/article/details/108566416
jps命令 :(Java Virtual Machine Process Status Tool)是JDK 1.5提供的一个显示当前所有java进程pid的命令。jps存放在JAVA_HOME/bin/jps,使用时为了方便请将JAVA_HOME/bin/加入到Path.会用 ps 命令也行。
常用的参数:
-q:只显示pid,不显示class名称,jar文件名和传递给main 方法的参数
-m:输出传递给main 方法的参数,在嵌入式jvm上可能是null
-l:输出应用程序main class的完整package名 或者 应用程序的jar文件完整路径名
-v: 输出传递给JVM的参数
示例:
[root@new-frame-251 texu]# jps -lv |grep 32528
32528 hoau-texu-service-0.0.1-SNAPSHOT-exec.jar -Xms80m -Xmx80m -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC -XX:+HeapDumpOnOutOfMemoryError
[root@new-frame-251 texu]#
jstack命令:Java提供的命令。可以查看某个进程的当前线程栈运行情况。根据这个命令的输出可以定位某个进程的所有线程的当前运行状态、运行代码,以及是否死锁等等。
[root@new-frame-251 texu]# jstack
Usage:
jstack [-l] <pid>
(to connect to running process)
jstack -F [-m] [-l] <pid>
(to connect to a hung process)
jstack [-m] [-l] <executable> <core>
(to connect to a core file)
jstack [-m] [-l] [server_id@]<remote server IP or hostname>
(to connect to a remote debug server)
Options:
-F to force a thread dump. Use when jstack <pid> does not respond (process is hung)
-m to print both java and native frames (mixed mode)
-l long listing. Prints additional information about locks
-h or -help to print this help message
[root@new-frame-251 texu]#
统计进程中线程个数:(linux 64位系统中jvm线程默认栈大小为1MB)
[root@new-frame-251 texu]# jstack 32528 |grep tid|wc -l
251
[root@new-frame-251 texu]#
查看进程中线程情况:
GC task thread :垃圾回收线程
http-nio thread :tomcat网络处理网络请求线程
C2CompilerThread :JIT编译线程,动态编译Java运行代码,C2表示编译的是server端代码
DubboServerHandler-10.39.251.159:20914-thread-200 : dubbo线程
还有很多其他的线程。。。
:
jmap命令: Java提供的命令。查看jvm内存使用情况。
jmap -heap [pid] : 查看整个JVM内存状态,要注意的是在使用CMS GC 情况下,jmap -heap的执行有可能会导致JAVA 进程挂起
jmap -histo [pid] : 查看JVM堆中对象详细占用情况
jmap -histo:live pid : 指定了live子选项,则只计算活动的对象
jmap -dump:format=b,file=文件名 [pid] : 导出整个JVM 中内存信息
......
jvisualvm.exe : java 自带的jvm监控工具,java的安装目录 bin/ 中
pmap命令: - report memory map of a process(查看进程的内存映像信息)pmap命令用于报告进程的内存映射关系
用法
pmap [ -x | -d ] [-q] pids...
pmap -V
选项含义
-x extended Show the extended format. 显示扩展格式
-d device Show the device format. 显示设备格式
-q quiet Do not display some header/footer lines. 不显示头尾行
-V show version Displays version of program. 显示版本
扩展格式和设备格式域:
Address: start address of map 映像起始地址
Kbytes: size of map in kilobytes 映像大小
RSS: resident set size in kilobytes 驻留集大小
Dirty: dirty pages (both shared and private) in kilobytes 脏页大小
Mode: permissions on map 映像权限: r=read, w=write, x=execute, s=shared, p=private (copy on write)
Mapping: file backing the map , or '[ anon ]' for allocated memory, or '[ stack ]' for the program stack. 映像支持文件,[anon]为已分配内存 [stack]为程序堆栈
Offset: offset into the file 文件偏移
Device: device name (major:minor) 设备名
jvm各项参数说明
-XX:MetaspaceSize=128m (元空间默认大小)
-XX:MaxMetaspaceSize=128m (元空间最大大小)
-Xms1024m (堆默认大小)此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmx1024m (堆最大大小)Java Heap最大值,默认值为物理内存的1/4,最佳设值应该视物理内存大小及计算机内其他内存开销而定
-Xmn256m (新生代大小)Java Heap Young区,堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss256k (棧最大深度大小)每个线程的Stack大小,JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
-XX:SurvivorRatio=8 (新生代分区比例 8:2)
-XX:+UseConcMarkSweepGC (指定使用的垃圾收集器,这里使用CMS收集器)
-XX:+PrintGCDetails (打印详细的GC日志)
JDK8之后把-XX:PermSize 和 -XX:MaxPermGen移除了,取而代之的是
-XX:MetaspaceSize=128m (元空间默认大小)
-XX:MaxMetaspaceSize=128m (元空间最大大小)
JDK 8开始把类的元数据放到本地化的堆内存(native heap)中,这一块区域就叫Metaspace,中文名叫元空间。
使用本地化的内存有什么好处呢?最直接的表现就是java.lang.OutOfMemoryError: PermGen 空间问题将不复存在,因为默认的类的元数据分配只受本地内存大小的限制,也就是说本地内存剩余多少,理论上Metaspace就可以有多大,这解决了空间不足的问题。不过,让Metaspace变得无限大显然是不现实的,因此我们也要限制Metaspace的大小:使用-XX:MaxMetaspaceSize参数来指定Metaspace区域的大小。JVM默认在运行时根据需要动态地设置MaxMetaspaceSize的大小。
排查步骤:
1. 通过 ps -aux |grep pid 或者 top -p pid 命令查看项目内存占用情况(我这里是已经优化完的)
RES:resident memory usage 常驻内存
(1)进程当前使用的内存大小,但不包括swap out
(2)包含其他进程的共享
(3)如果申请100m的内存,实际使用10m,它只增长10m,与VIRT相反
(4)关于库占用内存的情况,它只统计加载的库文件所占内存大小
RES = CODE + DATA
VIRT:virtual memory usage
(1)进程“需要的”虚拟内存大小,包括进程使用的库、代码、数据等
(2)假如进程申请100m的内存,但实际只使用了10m,那么它会增长100m,而不是实际的使用量
VIRT = SWAP + RES
2. 可以使用 jmap -dump:format=b,file=文件名 [pid] 导出整个JVM 中内存信息,再使用 jvisualvm 工具进行分析。或者使用 jmap -heap [pid] : 查看整个JVM内存状态。内存使用情况如下:
[root@new-frame-251 texu]# jmap -heap 5847
Attaching to process ID 5847, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.211-b12
using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC ##垃圾回收器
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 83886080 (80.0MB) ##当前jvm最大堆大小
NewSize = 27918336 (26.625MB)
MaxNewSize = 27918336 (26.625MB)
OldSize = 55967744 (53.375MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB) ##元空间大小
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB ## 最大元空间大小
G1HeapRegionSize = 0 (0.0MB)
Heap Usage: ## 当前堆的各个区域使用情况
New Generation (Eden + 1 Survivor Space):
capacity = 25165824 (24.0MB)
used = 5727184 (5.4618682861328125MB)
free = 19438640 (18.538131713867188MB)
22.757784525553387% used
Eden Space:
capacity = 22413312 (21.375MB)
used = 4922248 (4.694221496582031MB)
free = 17491064 (16.68077850341797MB)
21.961270159448098% used
From Space:
capacity = 2752512 (2.625MB)
used = 804936 (0.7676467895507812MB)
free = 1947576 (1.8573532104492188MB)
29.243687220982142% used
To Space:
capacity = 2752512 (2.625MB)
used = 0 (0.0MB)
free = 2752512 (2.625MB)
0.0% used
concurrent mark-sweep generation:
capacity = 55967744 (53.375MB)
used = 48057048 (45.830772399902344MB)
free = 7910696 (7.544227600097656MB)
85.86561573752195% used
28885 interned Strings occupying 3461280 bytes.
[root@new-frame-251 texu]# jstack 5847 |grep tid|wc -l ## 统计线程数,每个线程都有一个 tid
251
[root@new-frame-251 texu]# jstack 5847 |grep java.lang.Thread.State|wc -l ##有几个特殊的线程没有状态
246
[root@new-frame-251 texu]#
[root@master159 ~]# jstack 107435 |grep tid |wc -l
259
[root@master159 ~]# jstack 107435|grep 'dubbo-remoting-server-heartbeat' |wc -l
1
[root@master159 ~]# jstack 107435|grep 'DubboServerHandler-10.39.251.159:' |wc -l ##dubbo 线程有点多可以优化
200
[root@master159 ~]# jstack 107435|grep 'http-nio-10065-' |wc -l
14
[root@master159 ~]#
内存使用计算:(Eden )21.375+(扩展区1)2.625+(扩展区2)2.625+(垃圾回收器)53.375+(线程栈)251*0.25=142.8M
再加上 JVM进程本身运行内存+ NIO的DirectBuffer +JIT+JNI+…≈281M (最开始查看的项目占用内存,设置了-Xss256k,所以一个线程占用256k内存),计算出来很正常。
3. 查看进程中各个线程情况:
top -H -p 5847 中的 pid 的16进制转小写 对应的就是 jstack -l 5847 中的 nid 。使用这两个命令就可以查看线程的内存使用,及具体代码情况
4. 前面只是分析了RES 并没有分析 VIRT(虚拟内存),VIRT 分析如下:
使用 pmap -x 5847 查看进程的内存映像信息
这些内存块加起来就是虚拟内存占用的内存情况;
百度:linux为了解决多线程下内存分配竞争而引起的性能问题,增强了动态内存分配行为,使用了一种叫做arena的memory pool,在64位系统下面缺省配置是一个arena大小为64M,一个进程可以最多有cpu cores * 8个arena。假设机器是8核的,那么最多可以有8 * 8 = 64个arena,也就是会使用64 * 64 = 4096M内存。
可以通过设置系统环境变量来改变arena的数量:
MALLOC_ARENA_MAX=8(一般建议配置程序cpu核数)
现代操作系统里面分配虚拟地址空间操作不同于分配物理内存。在64位操作系统上,可用的最大虚拟地址空间有16EB,即大概180亿GB。那么在一台只有16G的物理内存的机器上,我也能要求获得4TB的地址空间以备将来使用
1.VIRT高是因为分配了太多地址空间导致。
2.一般来说不用太在意VIRT太高,因为你有16EB的空间可以使用。
3.如果你实在需要控制VIRT的使用,设置环境变量MALLOC_ARENA_MAX,例如hadoop推荐值为4,因为YARN使用VIRT值监控资源使用。参考:https://www.sohu.com/a/250985880_756465
结束:
以上只是对jvm内存占用情况进行分析,分析完之后,对异常的地方进行优化即可;
参考:
https://blog.csdn.net/zengwende/article/details/103665545
https://www.jianshu.com/p/8d5782bc596e