现象
1、机器内存不够用,已经达到80以上%
需求
需要知道机器上进程的占用内存情况
查询机器内存进程使用内存情况
查询命令
Linux 查询命令 top 查询正在运行的进程情况。进入监控后按i查询正在运行的进程。找到占内存比高的应用。应用是一个Java 进程内存使用率 35.2%。总内存32779904约等于32G,32g*0.352约等于10.1G
top - 10:47:41 up 80 days, 20:40, 1 user, load average: 0.10, 0.08, 0.10
Tasks: 110 total, 1 running, 109 sleeping, 0 stopped, 0 zombie
%Cpu(s): 1.7 us, 0.7 sy, 0.0 ni, 97.6 id, 0.0 wa, 0.0 hi, 0.1 si, 0.0 st
KiB Mem : 82.6/32779904 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ]
KiB Swap: 0.0/0 [ ]
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
11249 www 20 0 13.4g 11.0g 8048 S 1.0 35.2 718:00.75 java
2589 www 20 0 6393604 2.5g 7868 S 2.0 8.1 2458:26 java
22799 root 20 0 5983600 2.4g 7716 S 4.0 7.6 4880:49 java
2585 www 20 0 6213208 2.2g 7808 S 0.7 7.1 1243:40 java
2586 www 20 0 4929108 1.4g 7772 S 0.3 4.5 1324:14 java
2587 www 20 0 4893244 1.4g 7856 S 1.0 4.5 1234:17 java
19693 www 20 0 4820408 657000 16676 S 0.3 2.0 3:03.72 java
2091 root 20 0 1462680 54860 7424 S 0.3 0.2 389:44.95 icagent
2131 root 20 0 705708 11580 680 S 0.7 0.0 2:58.77 icwatchdog
3085 root 20 0 1421360 11224 6092 S 0.3 0.0 300:09.96 hostguard
现象
单个应用接口频繁(20W次左右)调用后应用所占内存持续增长。已经增长占用到10G内存。
调用前1G左右 ,20w次调用后 10G左右。
需求
需要单个应用的占用内存详情(线程占用内存,和其他信息),以进一步确定原因。
查询
查询命令
1、Linux线程占用资源情况
命令:top -H -p 进程号(top -H -p 11249)
top -H -p 端口
结果
top - 11:10:29 up 80 days, 21:02, 1 user, load average: 0.11, 0.10, 0.13
Threads: 90 total, 0 running, 90 sleeping, 0 stopped, 0 zombie
%Cpu(s): 1.8 us, 0.6 sy, 0.0 ni, 97.3 id, 0.1 wa, 0.0 hi, 0.2 si, 0.0 st
KiB Mem : 32779904 total, 238440 free, 26574892 used, 5966572 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 5703160 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
11417 www 20 0 13.4g 11.0g 8048 S 0.3 35.2 24:48.30 java
11249 www 20 0 13.4g 11.0g 8048 S 0.0 35.2 0:00.00 java
11250 www 20 0 13.4g 11.0g 8048 S 0.0 35.2 0:08.13 java
11251 www 20 0 13.4g 11.0g 8048 S 0.0 35.2 10:58.80 java
11252 www 20 0 13.4g 11.0g 8048 S 0.0 35.2 10:58.45 java
11253 www 20 0 13.4g 11.0g 8048 S 0.0 35.2 10:58.34 java
11254 www 20 0 13.4g 11.0g 8048 S 0.0 35.2 10:59.00 java
11255 www 20 0 13.4g 11.0g 8048 S 0.0 35.2 3:42.57 java
11256 www 20 0 13.4g 11.0g 8048 S 0.0 35.2 0:07.67 java
11257 www 20 0 13.4g 11.0g 8048 S 0.0 35.2 0:12.94 java
11258 www 20 0 13.4g 11.0g 8048 S 0.0 35.2 0:00.00 java
11259 www 20 0 13.4g 11.0g 8048 S 0.0 35.2 1:20.63 java
11360 www 20 0 13.4g 11.0g 8048 S 0.0 35.2 42:55.66 java
11361 www 20 0 13.4g 11.0g 8048 S 0.0 35.2 0:02.50 java
11362 www 20 0 13.4g 11.0g 8048 S 0.0 35.2 3:54.86 java
11363 www 20 0 13.4g 11.0g 8048 S 0.0 35.2 6:54.01 java
11364 www 20 0 13.4g 11.0g 8048 S 0.0 35.2 0:09.12 java
.......
分析
现象是 :应用中所有的线程都占用了总的内存大小。
猜测:占用多的部分在,是共享内存。(猜测问题的基础是,linx能够检测到Java进程内部使用资源的情况)
2、java应用占用资源情况(堆栈信息)
命令
jmap -heap 端口
jmap -heap 11249
结果
Attaching to process ID 11249, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.111-b14
using thread-local object allocation.
Parallel GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 1073741824 (1024.0MB)
NewSize = 357564416 (341.0MB)
MaxNewSize = 357564416 (341.0MB)
OldSize = 716177408 (683.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 354418688 (338.0MB)
used = 158615424 (151.2674560546875MB)
free = 195803264 (186.7325439453125MB)
44.75368522328033% used
From Space:
capacity = 1572864 (1.5MB)
used = 917600 (0.875091552734375MB)
free = 655264 (0.624908447265625MB)
58.339436848958336% used
To Space:
capacity = 1572864 (1.5MB)
used = 0 (0.0MB)
free = 1572864 (1.5MB)
0.0% used
PS Old Generation
capacity = 716177408 (683.0MB)
used = 341764080 (325.93162536621094MB)
free = 374413328 (357.06837463378906MB)
47.720589365477444% used
27253 interned Strings occupying 3025968 bytes.
分析
由 MaxHeapSize = 1073741824 (1024.0MB)
可知堆最大为1g。那么问题肯定不在堆空间中。
堆空间存放Java运行时创建的对象实例。
由MaxMetaspaceSize = 17592186044415 MB,可知元空间最大大小很大,随着程序的运行,元空间大小基本不受限制。
所以问题可能在于元空间占用了大部分内存。
元空间存放的信息:类的class信息,静态变量(基础类型),常量(基础类型),符号引用(方法引用)、字面量(字符串)等。
4、java应用的gc情况
命令
jstat -gc 端口 统计时间 统计次数
jstat -gc 11249 5s 3
结果
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
1536.0 1536.0 0.0 896.1 346112.0 36865.6 699392.0 333754.0 9889772.0 9885567.6 667904.0 667103.8 28492 750.752 19 9.213 759.965
1536.0 1536.0 0.0 896.1 346112.0 43401.3 699392.0 333754.0 9889772.0 9885567.6 667904.0 667103.8 28492 750.752 19 9.213 759.965
1536.0 1536.0 0.0 896.1 346112.0 48964.2 699392.0 333754.0 9889772.0 9885567.6 667904.0 667103.8 28492 750.752 19 9.213 759.965
分析
OC = 699392.0 当前老年代已分配内存
OU = 333754.0 当前老年代使用内存
MC =9889772.0 当前元空间已分配内存
MU =9885567.6 当前元空间使用内存
注意单位带小数点肯定不是字节,结合堆栈信息中的Old区,中used可知,当前单位是kb。所以呢元空间占用了近10g左右。
PS Old Generation
capacity = 716177408 (683.0MB)
used = 341764080 (325.93162536621094MB)
结论:元空间占用了大部分内存。
问题?元空间为什么占用了那么多的内存。
需求:知道元空间内部的存储情况。 但是目前没有办法。
由于目前不知道元空间内存情况,但是能够实时监控到元空间的总内存大小。且程序目标范围小,所以可以动态监控,找到出问题的程序。
执行程序动态监控排查问题
找到问题代码部分抽出来,用方法调用循环执行。
监控工具 jvisualvm.exe 。该工具在jdk自带的安装目录下bin文件夹中。
监控结果如下
证明该部分代码存在相关问题。
删除部分代码。继续执行。如果图形还是如上面符合一致性。就证明目标代码还在。如果如下面这种模型就是目标代码在删除的部分。
重上面办法就可以定位到代码片段。
最终确定代码为
mapper.map(vo, vo.getClass());
MemberDTO vo = new MemberDTO();
MapperFactory factory = new DefaultMapperFactory.Builder().build();
MapperFacade mapper = factory.getMapperFacade();
mapper.map(vo, vo.getClass()); //本行是有问题的代码,其他行是辅助
动态创建类时候发生的情况
猜测原因,中间创建了一批动态生成的class对象。导致元空间一值增加。
验证,找到类加载器创对象是后的class,看看是否有动态class对象产生。
构造器类对象debug Constructor
元空间垃圾中class信息回收条件
Java 堆永久代的回收
回收废弃常量与回收 Java 堆中的对象非常类似。
以常量池中字面量的回收为例,假如一个字符串"abc"已经进入了常量池中,但是当前系统没有任何一个 String 对象是叫做"abc"的,也没有其他地方引用了这个字面量,如果这时发生内存回收,而且必要的话,这个"abc"常量就会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。
类需要同时满足下面 3 个条件才能算是“无用的类”:
1. 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
2. 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
3. 加载该类的 ClassLoader 已经被回收。
动态生成的class对象肯定会有一串动态生产的代码。经过几次运行,判断该对象是动态产生的,顾定位到问题。
(MapperFacade )mapper.map(vo, vo.getClass());
每次执行该方法都会动态产生一个class对象导致方法区内存增加。且类加载器classLoader=appClassLoad,应用类加载器。
该类加载器不会被回收,所以class对象也不会被回收。所以元空间的内存会一直增加。类对象站的字节越大,调用次数越多,所占空间越大。
另外附带CGLib动态代理也会出现类似问题。debug如下
CGLib 元空间内存增长图
每代理一次,class名称就就有一个序号增长。