JAVA 应用线上故障排查手册

2022-10-24 15:00:14 浏览数 (3)

线上故障主要会包括 CPU、磁盘、内存(含JVM)以及网络问题,而大多数故障可能会包含不止一个层面的问题,所以进行排查时候尽量四个方面依次排查一遍。 基本上出问题就是 df、free、top、jstack、jmap具体问题具体分析

1. CPU

首先会排查 CPU 方面的问题。CPU 异常往往还是比较好定位的。原因包括业务逻辑问题(死循环)、频繁 gc 以及上下文切换过多。而最常见的往往是业务逻辑(或者框架)导致的,可以使用 jstack 来分析对应的堆栈情况。

1)使用 jstack 分析 CPU 问题

先用 ps 命令找到对应进程的 pid(如果你有好几个目标进程,可以先用 top 看一下哪个占用比较高。

代码语言:javascript复制
[root@VM_0_7_centos mblog]# top
top - 14:23:00 up 147 days,  2:54,  3 users,  load average: 0.00, 0.10, 0.16
Tasks: 119 total,   1 running, 118 sleeping,   0 stopped,   0 zombie
%Cpu(s):  1.7 us,  2.7 sy,  0.0 ni, 95.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  1882216 total,    74340 free,  1430976 used,   376900 buff/cache
KiB Swap:        0 total,        0 free,        0 used.   271692 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME  COMMAND                                                                                                      
12026 root      20   0  137648   7900   1116 S  0.7  0.4   1:17.51 YDService                                                                                                    
29517 root      20   0  638800  56188   3324 S  0.7  3.0  41:43.95 dockerd                                                                                                      
14232 root      20   0  742548  13408   1476 S  0.3  0.7 600:08.06 barad_agent                                                                                                  
21163 root      20   0 2488632  45992    252 S  0.3  2.4  90:06.64 java                                                                                                         
23464 root      20   0  157228   2520    812 S  0.3  0.1   0:34.20 sshd                                                                                                         
    1 root      20   0   43588   2808   1404 S  0.0  0.1  12:07.03 systemd                                                                                                      
    2 root      20   0       0      0      0 S  0.0  0.0   0:00.87 kthreadd                                                                                                     
    3 root      20   0       0      0      0 S  0.0  0.0   4:55.87 ksoftirqd/0                                                                                                  
    5 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 kworker/0:0H                                                                                                 
    7 root      rt   0       0      0      0 S  0.0  0.0   0:00.00 migration/0                                                                                                  
    8 root      20   0       0      0      0 S  0.0  0.0   0:00.00 rcu_bh                                                                                                       
    9 root      20   0       0      0      0 S  0.0  0.0  17:49.20 rcu_sched                                                                                                    
   10 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 lru-add-drain                                                                                             
   11 root      rt   0       0      0      0 S  0.0  0.0   0:48.47 watchdog/0      
   13 root      20   0       0      0      0 S  0.0  0.0   0:00.00 kdevtmpfs  

top -H -p pid来找到 CPU 使用率比较高的一些线程

代码语言:javascript复制
[root@VM_0_7_centos mblog]# top -H -p 26550
top - 14:24:31 up 147 days,  2:56,  3 users,  load average: 0.19, 0.13, 0.16
Threads:  48 total,   0 running,  48 sleeping,   0 stopped,   0 zombie
%Cpu(s):  2.3 us,  2.3 sy,  0.0 ni, 95.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  1882216 total,    70300 free,  1433760 used,   378156 buff/cache
KiB Swap:        0 total,        0 free,        0 used.   268880 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME  COMMAND                                                                                                      
26550 root      20   0 2581136 280592  12912 S  0.0 14.9   0:00.00 java                                                                                                         
26551 root      20   0 2581136 280592  12912 S  0.0 14.9   0:08.46 java                                                                                                         
26552 root      20   0 2581136 280592  12912 S  0.0 14.9   0:01.37 java                                                                                                         
26553 root      20   0 2581136 280592  12912 S  0.0 14.9   0:00.00 java                                                                                                         
26554 root      20   0 2581136 280592  12912 S  0.0 14.9   0:00.01 java                                                                                                         
26555 root      20   0 2581136 280592  12912 S  0.0 14.9   0:00.00 java                                                                                                         
26556 root      20   0 2581136 280592  12912 S  0.0 14.9   0:11.02 java                                                                                                         
26557 root      20   0 2581136 280592  12912 S  0.0 14.9   0:02.27 java                                                                                                         
26558 root      20   0 2581136 280592  12912 S  0.0 14.9   0:00.00 java                                                                                                         
26559 root      20   0 2581136 280592  12912 S  0.0 14.9   0:00.25 java                                                                                                         
26701 root      20   0 2581136 280592  12912 S  0.0 14.9   0:00.00 java                                                                                                         
26710 root      20   0 2581136 280592  12912 S  0.0 14.9   0:00.01 java                                                                                                         
26713 root      20   0 2581136 280592  12912 S  0.0 14.9   0:00.00 java    

然后在 jstack 中找到相应的堆栈信息jstack pid |grep 'nid' -C5

代码语言:javascript复制
[root@VM_0_7_centos mblog]# jstack 26550 |grep '0x67bf' -C5 
    - locked <0x00000000ecda6770> (a java.lang.ref.Reference$Lock)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=0 tid=0x00007fb7f8095000 nid=0x67b8 runnable 

"VM Periodic Task Thread" os_prio=0 tid=0x00007fb7f80de000 nid=0x67bf waiting on condition 

JNI global references: 1842

已经找到了 nid 为 0x67b6 的堆栈信息,接着只要仔细分析就行。 当然更常见的是对整个 jstack 文件进行分析,通常会比较关注 WAITING 和 TIMED_WAITING 的部分,BLOCKED 就不用说了。 可以使用命令cat jstack.log | grep "java.lang.Thread.State" | sort -nr | uniq -c来对 jstack 的状态有一个整体的把握,如果 WAITING 之类的特别多,那么多半是有问题啦。

2)频繁GC

是会使用 jstack 来分析问题,但有时候可以先确定下 gc 是不是太频繁,使用jstat -gc pid 1000命令来对 gc 分代变化情况进行观察,1000 表示采样间隔(ms),S0C/S1C、S0U/S1U、EC/EU、OC/OU、MC/MU 分别代表两个 Survivor 区、Eden 区、老年代、元数据区的容量和使用量。YGC/YGT、FGC/FGCT、GCT 则代表 YoungGc、FullGc 的耗时和次数以及总耗时。如果看到 gc 比较频繁,再针对 gc 方面做进一步分析

代码语言:javascript复制
[root@VM_0_7_centos mblog]# jstat -gc 26550  1000
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
3008.0 3008.0  0.0   1902.1 24384.0   6904.6   60608.0    38192.6   83496.0 79692.4 11304.0 10656.9    259    1.081   5      0.365    1.446
3008.0 3008.0  0.0   1902.1 24384.0   6904.6   60608.0    38192.6   83496.0 79692.4 11304.0 10656.9    259    1.081   5      0.365    1.446
3008.0 3008.0  0.0   1902.1 24384.0   6904.6   60608.0    38192.6   83496.0 79692.4 11304.0 10656.9    259    1.081   5      0.365    1.446
3008.0 3008.0  0.0   1902.1 24384.0   6904.6   60608.0    38192.6   83496.0 79692.4 11304.0 10656.9    259    1.081   5      0.365    1.446
3008.0 3008.0  0.0   1902.1 24384.0   6904.6   60608.0    38192.6   83496.0 79692.4 11304.0 10656.9    259    1.081   5      0.365    1.446
3008.0 3008.0  0.0   1902.1 24384.0   7968.7   60608.0    38192.6   83496.0 79692.4 11304.0 10656.9    259    1.081   5      0.365    1.446
3008.0 3008.0  0.0   1902.1 24384.0   7968.7   60608.0    38192.6   83496.0 79692.4 11304.0 10656.9    259    1.081   5      0.365    1.446
3)上下文切换

使用vmstat命令来进行查看

代码语言:javascript复制
[root@VM_0_7_centos mblog]# vmstat 26550
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 3  0      0  80832   7996 356880    0    0    17    37    2    2  1  1 99  0  0

cs(context switch)一列则代表了上下文切换的次数。 如果希望对特定的 pid 进行监控那么可以使用 pidstat -w pid命令,cswch 和 nvcswch 表示自愿及非自愿切换。

代码语言:javascript复制
[root@VM_0_7_centos mblog]# pidstat  -w 26550
Linux 3.10.0-957.21.3.el7.x86_64 (VM_0_7_centos)     05/15/2020  _x86_64_    (1 CPU)

03:06:22 PM   UID       PID   cswch/s nvcswch/s  Command
03:07:04 PM     0         1      0.48      0.02  systemd
03:07:04 PM     0         3      1.84      0.00  ksoftirqd/0
03:07:04 PM     0         9     22.27      0.00  rcu_sched
03:07:04 PM     0        11      0.26      0.00  watchdog/0
03:07:04 PM     0        30      0.10      0.00  kswapd0
03:07:04 PM     0        99      0.24      0.00  kauditd
03:07:04 PM     0      1190      1.65      0.00  kworker/0:1H
03:07:04 PM     0      1281      0.60      0.00  jbd2/vda1-8
03:07:04 PM     0      1372      0.19      0.00  systemd-journal
03:07:04 PM   998      2534      0.07      0.00  lsmd
03:07:04 PM     0      2541      0.43      0.00  systemd-logind
03:07:04 PM    81      2542      0.81      0.00  dbus-daemon
03:07:04 PM     0      4631      0.05      0.00  sgagent
03:07:04 PM     0      6807      0.05      0.00  sshd
03:07:04 PM    38      7063      0.07      0.00  ntpd
03:07:04 PM     0      7311      0.05      0.00  crond
03:07:04 PM     0      8426      0.22      0.00  auditd
03:07:04 PM     0      9731      0.53      0.00  kworker/0:1
03:07:04 PM     0     12026      0.86      0.00  YDService
03:07:04 PM     0     12050      0.02      0.00  pidstat

2. 磁盘

直接使用df -hl来查看文件系统状态

代码语言:javascript复制
[root@VM_0_7_centos mblog]# df  -hl
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        909M     0  909M   0% /dev
tmpfs           920M   32K  920M   1% /dev/shm
tmpfs           920M  988K  919M   1% /run
tmpfs           920M     0  920M   0% /sys/fs/cgroup
/dev/vda1        50G   11G   37G  22% /
tmpfs           184M     0  184M   0% /run/user/0
overlay          50G   11G   37G  22% /var/lib/docker/overlay2/6d3ac4731796a1e5eb0408384051439ef0e04fc1b0da8352f962ac0de5f79961/merged
shm              64M     0   64M   0% /var/lib/docker/containers/9a8b43b48d28261f7ca6a42269617b3360928c6f98e16cc0e0e2734f3aabbf09/mounts/shm
overlay          50G   11G   37G  22% /var/lib/docker/overlay2/356fa3e9b02cdb6249d63ee71f310083145f4e46b755b48f4c6b9e3312f5ed5b/merged
overlay          50G   11G   37G  22% /var/lib/docker/overlay2/d116b2316f31442a3b016bdfc948c21abb34e2c5334e6fca3f2b5300f2557afc/merged
overlay          50G   11G   37G  22% /var/lib/docker/overlay2/489a913865b84fd119290baef3f27fa84a4539172e5ca3d553553d19a1140b30/merged
shm              64M     0   64M   0% /var/lib/docker/containers/0aa0b495b88586620f95924fc2ec2ddf6c789f6c79b119b402851e17b839e656/mounts/shm
shm              64M     0   64M   0% /var/lib/docker/containers/ee708e3216dba7cd6284b0fef0115fcb0adb1e550b7357485af1fcdfc9942d1e/mounts/shm
shm              64M     0   64M   0% /var/lib/docker/containers/b844cd76ce1dc12aa6396fd7ef105d30c10c1a4cbc2559a37b87f650ea8a2a83/mounts/shm
overlay          50G   11G   37G  22% /var/lib/docker/overlay2/1c3f63084bb0845336968aef1b4179bfe0e7b3dd9904867a5b3a5805708312e1/merged
shm              64M     0   64M   0% /var/lib/docker/containers/1877103ed43f6dea45b98ad3f763d2d562b80e06125fa7b10350f7e42a16f408/mounts/shm
overlay          50G   11G   37G  22% /var/lib/docker/overlay2/3dd57c5b2119b45901d6b022e7b38d9f45c3fb6472c983a5d9cb598943eb7a99/merged
overlay          50G   11G   37G  22% /var/lib/docker/overlay2/64411f1ae1a535e4568d00e9de041de2a6c09f896f2cf666f752b684e84da35f/merged
shm              64M     0   64M   0% /var/lib/docker/containers/1e055f70660dbe8d3060ac34ec26347b310f857d61cadcc6cc3b5973023379a6/mounts/shm
shm              64M     0   64M   0% /var/lib/docker/containers/f555d33b406a2168ce91536bad302179d3cb8e621d4aa8616c4c08baf235d3f3/mounts/shm

磁盘问题还是性能上的问题,通过 iostat -d -k -x来进行分析

代码语言:javascript复制
[root@VM_0_7_centos mblog]# iostat -d -k -x
Linux 3.10.0-957.21.3.el7.x86_64 (VM_0_7_centos)     05/15/2020  _x86_64_    (1 CPU)

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
vda               0.01     2.72    0.41    4.86    16.90    36.95    20.44     0.03    7.13   23.12    5.79   0.33   0.18
scd0              0.00     0.00    0.00    0.00     0.00     0.00    67.86     0.00    0.44    0.44    0.00   0.33   0.00

另外还需要知道是哪个进程在进行读写,或者用 iotop 命令来进行定位文件读写的来源。

代码语言:javascript复制
[root@VM_0_7_centos mblog]# iotop 

Total DISK READ :       0.00 B/s | Total DISK WRITE :       0.00 B/s
Actual DISK READ:       0.00 B/s | Actual DISK WRITE:       0.00 B/s
  TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND                                                                                                           
    1 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % systemd --system --deserialize 22
    2 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [kthreadd]
    3 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [ksoftirqd/0]
    5 be/0 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [kworker/0:0H]
    7 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [migration/0]
    8 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [rcu_bh]
    9 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [rcu_sched]
   10 be/0 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [lru-add-drain]
   11 rt/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [watchdog/0]
   13 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [kdevtmpfs]
   14 be/0 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [netns]
   15 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [khungtaskd]
   16 be/0 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [writeback]
   17 be/0 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [kintegrityd]
   18 be/0 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [bioset]
   19 be/0 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [bioset]
   20 be/0 root        0.00 B/s    0.00 B/s  0.00 %  0.00 % [bioset]

不过这边拿到的是 tid,要转换成 pid,可以通过 readlink 来找到 pid

代码语言:javascript复制
readlink -f /proc/*/task/tid/../..

找到 pid 之后就可以看这个进程具体的读写情况cat /proc/pid/io

代码语言:javascript复制
[root@VM_0_7_centos mblog]# readlink -f /proc/1/
/proc/1
[root@VM_0_7_centos mblog]# cat /proc/1/io 
rchar: 322196383282
wchar: 45154675778
syscr: 400078452
syscw: 97216260
read_bytes: 156394731008
write_bytes: 39740551168
cancelled_write_bytes: 1688125440

还可以通过 lsof 命令来确定具体的文件读写情况lsof -p pid

代码语言:javascript复制
[root@VM_0_7_centos mblog]# lsof -p 1
COMMAND PID USER   FD      TYPE             DEVICE SIZE/OFF       NODE NAME
systemd   1 root  cwd       DIR              253,1     4096          2 /
systemd   1 root  rtd       DIR              253,1     4096          2 /
systemd   1 root  txt       REG              253,1  1624512      12010 /usr/lib/systemd/systemd
systemd   1 root  mem       REG              253,1    20064       4637 /usr/lib64/libuuid.so.1.3.0
systemd   1 root  mem       REG              253,1   265600      17823 /usr/lib64/libblkid.so.1.1.0
systemd   1 root  mem       REG              253,1    90248       4230 /usr/lib64/libz.so.1.2.7
systemd   1 root  mem       REG              253,1   157424       4993 /usr/lib64/liblzma.so.5.2.2
systemd   1 root  mem       REG              253,1    23968       5358 /usr/lib64/libcap-ng.so.0.0.0
systemd   1 root  mem       REG              253,1    19896       4329 /usr/lib64/libattr.so.1.1.0
systemd   1 root  mem       REG              253,1    19288       3928 /usr/lib64/libdl-2.17.so
systemd   1 root  mem       REG              253,1   402384       4410 /usr/lib64/libpcre.so.1.2.0
systemd   1 root  mem       REG              253,1  2156160       3908 /usr/lib64/libc-2.17.so
systemd   1 root  mem       REG              253,1   142232       3935 /usr/lib64/libpthread-2.17.so
systemd   1 root  mem       REG              253,1    88776      11356 /usr/lib64/libgcc_s-4.8.5-20150702.so.1
systemd   1 root  mem       REG              253,1    43776      17769 /usr/lib64/librt-2.17.so
systemd   1 root  mem       REG              253,1   277808       8396 /usr/lib64/libmount.so.1.1.0
systemd   1 root  mem       REG              253,1    91800      12919 /usr/lib64/libkmod.so.2.2.10
systemd   1 root  mem       REG              253,1   127184       5361 /usr/lib64/libaudit.so.1.0.0
systemd   1 root  mem       REG              253,1    61672       8507 /usr/lib64/libpam.so.0.83.1

3. 内存

内存问题排查起来相对比 CPU 麻烦一些,场景也比较多。主要包括 OOM、GC 问题和堆外内存。一般来讲,会先用free命令先来检查一发内存的各种情况。

代码语言:javascript复制
[root@VM_0_7_centos mblog]# free
              total        used        free      shared  buff/cache   available
Mem:        1882216     1437360       82932       15452      361924      266016
Swap: 
1)堆内内存

内存问题大多还都是堆内内存问题。表象上主要分为 OOM 和 Stack Overflo。

2)OOM

JVM中的内存不足,OOM 大致可以分为以下几种:

代码语言:javascript复制
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

这个意思是没有足够的内存空间给线程分配 Java 栈,基本上还是线程池代码写的有问题,比如说忘记 shutdown,所以说应该首先从代码层面来寻找问题, 使用 jstack 或者 jmap。如果一切都正常,JVM 方面可以通过指定Xss来减少单个 thread stack 的大小。 另外也可以在系统层面,可以通过修改/etc/security/limits.confnofile 和 nproc 来增大系统对线程的限制,AES除外和severless除外

代码语言:javascript复制
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

这个意思是堆的内存占用已经达到-Xmx 设置的最大值,应该是最常见的 OOM 错误了。解决思路仍然是先应该在代码中找,怀疑存在内存泄漏,通过 jstack 和 jmap 去定位问题。如果说一切都正常,才需要通过调整Xmx的值来扩大内存。

代码语言:javascript复制
Caused by: java.lang.OutOfMemoryError: Meta space

这个意思是元数据区的内存占用已经达到XX:MaxMetaspaceSize设置的最大值,排查思路和上面的一致,参数方面可以通过XX:MaxPermSize来进行调整(这里就不说 1.8 以前的永久代了,1.8之后是元数据区)。

Stack Overflow

栈内存溢出

代码语言:javascript复制
Exception in thread "main" java.lang.StackOverflowError

表示线程栈需要的内存大于 Xss 值,同样也是先进行排查,参数方面通过Xss来调整,但调整的太大可能又会引起 OOM。

3)使用JMAP 定位代码内存泄漏

上述关于 OOM 和 Stack Overflo 的代码排查方面,一般使用 JMAP jmap -dump:format=b,file=filename pid来导出 dump 文件clear

代码语言:javascript复制
[root@VM_0_7_centos mblog]# jmap -dump:format=b,file=filename 26550
Dumping heap to /usr/local/mblog/filename ...
File exists
[root@VM_0_7_centos mblog]# 
filename 

通过 mat(Eclipse Memory Analysis Tools;IDEA我还没找到合适的工具)导入 dump 文件进行分析,内存泄漏问题一般直接选 Leak Suspects 即可,mat 给出了内存泄漏的建议。另外也可以选择 Top Consumers 来查看最大对象报告(知识盲点,只听说过,有空研究)和线程相关的问题可以选择 thread overview 进行分析。 日常开发中,代码产生内存泄漏是比较常见的事,并且比较隐蔽,需要开发者更加关注细节。比如说每次请求都 new 对象,导致大量重复创建对象;进行文件流操作但未正确关闭;手动不当触发 gc;ByteBuffer 缓存分配不合理等都会造成代码 OOM。另一方面,可以在启动参数中指定-XX: HeapDumpOnOutOfMemoryError来保存 OOM 时的 dump 文件。

4)gc 问题和线程

gc 问题除了影响 CPU 也会影响内存,排查思路也是一致的。一般先使用 jstat 来查看分代变化情况,比如 youngGC 或者 fullGC 次数是不是太多呀;EU、OU 等指标增长是不是异常呀等。线程的话太多而且不被及时 gc 也会引发 oom,大部分就是之前说的unable to create new native thread。除了 jstack 细细分析 dump 文件外,一般先会看下总体线程,通过pstreee -p pid |wc -l。

代码语言:javascript复制
[root@VM_0_7_centos mblog]# pstree -p 26550 |wc -l
52
5)堆外内存

如果碰到堆外内存溢出,那可真是惨。首先堆外内存溢出表现就是物理常驻内存增长快,报错的话视使用方式都不确定, 如果由于使用 Netty 导致的,那错误日志里可能会出现OutOfDirectMemoryError错误,注意一下是否是线程阻塞了 如果直接是 DirectByteBuffer,那会报OutOfMemoryError: Direct buffer memory。 堆外内存溢出往往是和 NIO 的使用相关,一般先通过 pmap 来查看下进程占用的内存情况pmap -x pid | sort -rn -k3 | head -30, 这段意思是查看对应 pid 倒序前 30 大的内存段。这边可以再一段时间后再跑一次命令看看内存增长情况,或者和正常机器比较可疑的内存段在哪里。

代码语言:javascript复制
[root@VM_0_7_centos mblog]# pmap -x 26550 | sort -rn -k3 | head -30
total kB         2588336  292516  285368
00000000ecd50000   60608   49504   49504 rw---   [ anon ]
00007fb7c8000000   37012   37004   37004 rw---   [ anon ]
00000000e3400000   30400   28192   28192 rw---   [ anon ]
00007fb7cc000000   24560   24320   24320 rw---   [ anon ]
00007fb7f8000000   23724   23420   23420 rw---   [ anon ]
00007fb7e9000000   20992   20388   20388 rwx--   [ anon ]
0000000100000000   11304   11228   11228 rw---   [ anon ]
00007fb7fe52a000    9028    9012    9012 rw---   [ anon ]
00007fb7fffcc000   13216    6108       0 r-x-- libjvm.so
00007fb7dc000000    4036    4028    4028 rw---   [ anon ]
00007fb7c0000000   11164    3028    3028 rw---   [ anon ]
00007fb7e4000000    2444    2444    2444 rw---   [ anon ]
00007fb7c70ae000    3064    2164    2164 rw---   [ anon ]
00007fb7e80ec000    3064    2140    2140 rw---   [ anon ]
00007fb7d55d8000    3064    2140    2140 rw---   [ anon ]
00007fb7c68aa000    3064    2112    2112 rw---   [ anon ]
00007fb7fd7d9000    2048    2048    2048 rw---   [ anon ]
00007fb7fd5d9000    2048    2048    2048 rw---   [ anon ]
00007fb7fca8f000    2048    2048    2048 rw---   [ anon ]
00007fb7fc88f000    2048    2048    2048 rw---   [ anon ]
00007fb7fc68f000    2048    2048    2048 rw---   [ anon ]
00007fb7fc48f000    2048    2048    2048 rw---   [ anon ]
00007fb7fc28f000    2048    2048    2048 rw---   [ anon ]
00007fb7fc08f000    2048    2048    2048 rw---   [ anon ]
00007fb7e8e00000    2048    2048    2048 rw---   [ anon ]
00007fb7e8c00000    2048    2048    2048 rw---   [ anon ]
00007fb7e8a00000    2048    2048    2048 rw---   [ anon ]
00007fb7d58d6000    2048    2048    2048 rw---   [ anon ]
00007fb7d53d5000    2048    2048    2048 rw---   [ anon ]

4. GC 问题

堆内内存泄漏总是和 GC 异常相伴。不过 GC 问题不只是和内存问题相关,还有可能引起 CPU 负载、网络问题等系列并发症,只是相对来说和内存联系紧密些,所以在此单独总结一下 GC 相关问题。在 CPU 章介绍了使用 jstat 来获取当前 GC 分代变化信息。而更多时候,是通过 GC 日志来排查问题的,在启动参数中加上-verbose:gc -XX: PrintGCDetails -XX: PrintGCDateStamps -XX: PrintGCTimeStamps来开启 GC 日志。 常见的 Young GC、Full GC 日志含义在此就不做赘述了。 针对 gc 日志,就能大致推断出 youngGC 与 fullGC 是否过于频繁或者耗时过长。对 G1 垃圾收集器来做分析,建议使用 G1-XX: UseG1GC。

1)youngGC 过频繁

youngGC 频繁一般是短周期小对象较多,先考虑是不是 Eden 区/新生代设置的太小了,看能否通过调整-Xmn、-XX:SurvivorRatio 等参数设置来解决问题。如果参数正常,但是 young gc 频率还是太高,就需要使用 Jmap 和 MAT 对 dump 文件进行进一步排查了。

2)youngGC 耗时过长

耗时过长问题就要看 GC 日志里耗时耗在哪一块了。以 G1 日志为例,可以关注 Root Scanning、Object Copy、Ref Proc 等阶段。Ref Proc 耗时长,就要注意引用相关的对象。Root Scanning 耗时长,就要注意线程数、跨代引用。 Object Copy 则需要关注对象生存周期。而且耗时分析它需要横向比较,就是和其他项目或者正常时间段的耗时比较。比如说图中的 Root Scanning 和正常时间段比增长较多,那就是起的线程太多了。

3)触发 fullGC

G1 中更多的还是 mixedGC,但 mixedGC 可以和 youngGC 思路一样去排查。触发 fullGC 了一般都会有问题,G1 会退化使用 Serial 收集器来完成垃圾的清理工作,暂停时长达到秒级别,可以说是半跪了。 fullGC 的原因可能包括以下这些,以及参数调整方面的一些思路: 并发阶段失败:在并发标记阶段,MixGC 之前老年代就被填满了,那么这时候 G1 就会放弃标记周期。这种情况,可能就需要增加堆大小,或者调整并发标记线程数-XX:ConcGCThreads。 晋升失败:在 GC 的时候没有足够的内存供存活/晋升对象使用,所以触发了 Full GC。这时候可以通过-XX:G1ReservePercent来增加预留内存百分比,减少-XX:InitiatingHeapOccupancyPercent来提前启动标记,-XX:ConcGCThreads来增加标记线程数也是可以的。 大对象分配失败:大对象找不到合适的 region 空间进行分配,就会进行 fullGC,这种情况下可以增大内存或者增大-XX:G1HeapRegionSize。 程序主动执行 System.gc():不要随便写就对了。

作者:我超有耐心的

源链接:https://juejin.cn/post/6844904159913705485

格式整理:IT运维技术圈

0 人点赞