作者:京东科技 徐传乐

布景

在高并发下,Java程序的GC问题归于很典型的一类问题,带来的影响往往会被进一步扩大。不管是「GC频率过快」还是「GC耗时太长」,因为GC期间都存在Stop The World问题,因而很简单导致服务超时,引发性能问题。

事情开端是线上某运用废物搜集呈现Full GC反常的现象,运用中单个实例Full GC时刻特别长,持续时刻约为15~30秒,均匀每2周左右触发一次;



一次JVM GC长暂停的排查过程



一次JVM GC长暂停的排查过程



JVM参数装备“-Xms2048M –Xmx2048M –Xmn1024M –XX:MaxPermSize=512M”



一次JVM GC长暂停的排查过程



排查进程

剖析GC 日志

GC 日志它记录了每一次的 GC 的执行时刻和执行成果,经过剖析 GC 日志能够调优堆设置和 GC 设置,或者改进运用程序的目标分配模式。

这里Full GC的reason是Ergonomics,是因为敞开了UseAdaptiveSizePolicy,jvm自己进行自适应调整引发的Full GC。

这份日志主要表现GC前后的改变,目前为止看不出个所以然来。



一次JVM GC长暂停的排查过程



敞开GC日志,需求增加如下 JVM 发动参数:

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/export/log/risk_pillar/gc.log

常见的 Young GC、Full GC 日志意义如下:



一次JVM GC长暂停的排查过程



进一步检查服务器性能目标

获取到了GC耗时的时刻后,经过监控渠道获取到各个监控项,开端排查这个时点有反常的目标,最终剖析发现,在5.06分左右(GC的时点),CPU占用明显提升,而SWAP呈现了开释资源、memory资源增加呈现拐点的状况(详见下图红色框,橙色框中的改变是因修改装备导致,后面会介绍,暂且可忽略)



一次JVM GC长暂停的排查过程



JVM用到了swap?是因为GC导致的CPU突然飙升,而且开释了swap交流区这部本分存到memory?

为了验证JVM是否用到swap,咱们经过检查proc下的进程内存资源占用状况

| for i in (cd/proc;ls∣grep”[0−9]”∣awk′( cd /proc;ls |grep “^[0-9]”|awk ‘ 0 >100′) ;do awk ‘/Swap:/{a=a+2}END{print ‘”i”‘,a/1024″M”}’ /proc/$i/smaps 2>/dev/null ; done | sort -k2nr | head -10 # head -10 表明 取出 前10个内存占用高的进程 # 取出的榜首列为进程的id 第二列进程占用swap巨细 | | ———————————————————————————————————————————————————————————————————————————— |

看到的确有用到305MB的swap



一次JVM GC长暂停的排查过程



这里简略介绍下什么是swap?

swap指的是一个交流分区或文件,主要是在内存运用存在压力时,触发内存收回,这时可能会将部本分存的数据交流到swap空间,以便让体系不会因为内存不够用而导致oom或者更致命的状况呈现。

当某进程向OS请求内存发现不足时,OS会把内存中暂时不必的数据交流出去,放在swap分区中,这个进程称为swap out。

当某进程又需求这些数据且OS发现还有空闲物理内存时,又会把swap分区中的数据交流回物理内存中,这个进程称为swap in。



为了验证GC耗时与swap操作有必然联系,我抽查了十几台机器,重点关注耗时长的GC日志,经过时刻点确认到GC耗时的时刻点与swap操作的时刻点的确是共同的。

进一步检查虚拟机各实例 swappiness 参数,一个普遍现象是,凡是产生较长Full GC的实例都装备了参数 vm.swappiness = 30(值越大表明越倾向于运用swap);而GC时刻相对正常的实例装备参数 vm.swappiness = 0(最大极限地下降运用swap)。

swappiness 能够设置为 0 到 100 之间的值,它是Linux的一个内核参数,控制体系在进 行swap时,内存运用的相对权重。

swappiness=0: 表明最大极限运用物理内存,然后才是 swap空间

swappiness=100: 表明积极的运用swap分区,而且把内存上的数据及时的交流到swap空间里面



一次JVM GC长暂停的排查过程



一次JVM GC长暂停的排查过程



对应的物理内存运用率和swap运用状况如下



一次JVM GC长暂停的排查过程



一次JVM GC长暂停的排查过程



至此,锋芒好像都指向了swap。

问题剖析

当内存运用率到达水位线(vm.swappiness)时,linux会把一部分暂时不运用的内存数据放到磁盘swap去,以便腾出更多可用内存空间;

当需求运用坐落swap区的数据时,再将其换回内存中,当JVM进行GC时,需求对相应堆分区的已用内存进行遍历;

假设GC的时分,有堆的一部本分容被交流到swap空间中,遍历到这部分的时分就需求将其交流回内存,因为需求访问磁盘,所以比较物理内存,它的速度必定慢的令人发指,GC停顿的时刻一定会十分十分恐惧;

从而导致Linux对swap分区的收回滞后(内存到磁盘换入换出操作十分占用CPU与体系IO),在高并发/QPS服务中,这种滞后带来的成果是致命的(STW)。

问题处理

至此,答案好像很清晰,咱们只需尝试把swap封闭或开释掉,看看能否处理问题?

怎么开释swap?

  1. 设置vm.swappiness=0(重启运用开释swap后收效),表明尽可能不运用交流内存

a、 临时设置计划,重启后不收效

设置vm.swappiness为0

sysctl vm.swappiness=0

检查swappiness值

cat /proc/sys/vm/swappiness

b、 永久设置计划,重启后依然收效

vi /etc/sysctl.conf

增加

vm.swappiness=0

  1. 封闭交流分区swapoff –a

前提:首先要保证内存剩下要大于等于swap运用量,不然会报Cannot allocate memory!swap分区一旦开释,所有寄存在swap分区的文件都会转存到物理内存上,可能会引发体系IO或者其他问题。

a、 检查当前swap分区挂载在哪?



一次JVM GC长暂停的排查过程



b、 关停分区



一次JVM GC长暂停的排查过程



封闭swap交流区后的内存改变见下图橙色框,此时swap分区的文件都转存到了物理内存上



一次JVM GC长暂停的排查过程



封闭Swap交流区后,于2.23再次产生Full GC,耗时190ms,问题得到处理。



一次JVM GC长暂停的排查过程



疑惑

1、 是不是只需敞开了swap交流区的JVM,在GC的时分都会耗时较长呢?

2、 既然JVM对swap如此不待见,为何JVM不明令禁止运用呢?

3、 swap作业机制是怎样的?这台物理内存为8g的server,运用了交流区内存(swap),阐明物理内存不够运用了,但是经过free指令检查内存运用状况,实际物理内存好像并没有占用那么多,反而Swap已占近1G?



一次JVM GC长暂停的排查过程



free:除了buff/cache剩下了多少内存

shared:同享内存

buff/cache:缓冲、缓存区内存数(运用过高通常是程序频繁存取文件)

available:真实剩下的可用内存数


咱们能够想想,封闭交流磁盘缓存意味着什么?

其实大可不必如此急进,要知道这个国际永远不是非0即1的,咱们都会或多或少挑选走在中间,不过有些偏向0,有些偏向1罢了。

很显然,在swap这个问题上,JVM能够挑选偏向尽量少用,从而下降swap影响,要下降swap影响有必要弄清楚Linux内存收回是怎么作业的,这样才干不遗漏任何可能的疑点。

先来看看swap是怎么触发的?

Linux会在两种场景下触发内存收回,一种是在内存分配时发现没有足够空闲内存时会立刻触发内存收回;另一种是敞开了一个守护进程(kswapd进程)周期性对体系内存进行检查,在可用内存下降到特定阈值之后主动触发内存收回。

经过如下图示能够很简单了解,详细信息拜见:hbasefly.com/2017/05/24/…



一次JVM GC长暂停的排查过程



回答是不是只需敞开了swap交流区的JVM,在GC的时分都会耗时较长

笔者去查了一下另外的一个运用,相关目标信息请见下图。

实名服务的QPS是十分高的,同样能看到运用了swap,GC均匀耗时 576ms,这是为什么呢?



一次JVM GC长暂停的排查过程



一次JVM GC长暂停的排查过程



经过把时刻规模聚焦到产生GC的某一时刻段,从监控目标图能够看到swapUsed没有任何改变,也就是说没有swap活动,从而没有影响到垃级收回的总耗时。



一次JVM GC长暂停的排查过程





一次JVM GC长暂停的排查过程



经过如下指令列举出各进程swap空间占用状况,很清楚的看到实名这个服务swap空间占用的较少(仅54.2MB)



一次JVM GC长暂停的排查过程



另一个明显的现象是实名服务Full GC距离较短(几个小时一次),而我的服务均匀距离2周一次Full GC



一次JVM GC长暂停的排查过程





一次JVM GC长暂停的排查过程



根据以上估测

1、 实名服务因为 GC 距离较短,内存中的东西底子没有机会置换到swap中就被收回了,GC的时分不需求将swap分区中的数据交流回物理内存中,完全根据内存计算,所以要快很多

2、 将哪些内存数据置换进swap交流区的筛选策略应该是类似于LRU算法(最近最少运用准则)

为了证实上述猜想,咱们只需跟踪swap变更日志,监控数据改变即可得到答案,这里采用一段shell 脚本实现

#!/bin/bash 
echo -e `date +%y%m%d%H%M%S` 
echo -e "PID\t\tSwap\t\tProc_Name" 
#拿出/proc目录下所有以数字为名的目录(进程名是数字才是进程,其他如sys,net等寄存的是其他信息) 
for pid in `ls -l /proc | grep ^d | awk '{ print $9 }'| grep -v [^0-9]` 
do 
    if [ $pid -eq 1 ];then continue;fi 
    grep -q "Swap" /proc/$pid/smaps 2>/dev/null 
    if [ $? -eq 0 ];then 
        swap=$(gawk '/Swap/{ sum+=$2;} END{ print sum }' /proc/$pid/smaps) #统计占用的swap分区的 巨细 单位是KB 
        proc_name=$(ps aux | grep -w "$pid" | awk '!/grep/{ for(i=11;i<=NF;i++){ printf("%s ",$i); }}') #取出进程的名字 
        if [ $swap -gt 0 ];then #判断是否占用swap 只要占用才会输出 
            echo -e "${pid}\t${swap}\t${proc_name:0:100}" 
    fi 
   fi
done | sort -k2nr | head -10 | gawk -F'\t' '{ #排序取前 10 
    pid[NR]=$1; 
    size[NR]=$2; 
    name[NR]=$3; 
} 
END{ 
    for(id=1;id<=length(pid);id++) 
    { 
    if(size[id]<1024) 
        printf("%-10s\t%15sKB\t%s\n",pid[id],size[id],name[id]); 
    else if(size[id]<1048576) 
        printf("%-10s\t%15.2fMB\t%s\n",pid[id],size[id]/1024,name[id]);
    else 
    printf("%-10s\t%15.2fGB\t%s\n",pid[id],size[id]/1048576,name[id]); 
    } 
}'

因为上面图中 2022.3.2 19:57:00 至 2022.3.2 19:58:00 产生了一次Full GC,咱们重点关注下这一分钟内swap交流区的改变即可,我这里每10s做一次信息采集,能够看到在GC时点前后,swap的确没有改变 

一次JVM GC长暂停的排查过程



经过上述剖析,回归本文核心问题上,现在看来我的处理方式过于急进了,其实也能够不必封闭swap,经过恰当下降堆巨细,也是能够处理问题的。

这也旁边面的阐明,布置Java服务的Linux体系,在内存分配上并不是无脑大而全,需求综合考虑不同场景下JVM对Java永久代 、Java堆(新生代和老时代)、线程栈、Java NIO所运用内存的需求。

总结

综上,咱们得出结论,swap和GC同一时分产生会导致GC时刻十分长,JVM严峻卡顿,极点的状况下会导致服务溃散。

主要原因是:JVM进行GC时,需求对对应堆分区的已用内存进行遍历,假设GC的时分,有堆的一部本分容被交流到swap中,遍历到这部分的时分就须要将其交流回内存;更极点状况同一时刻因为内存空间不足,就需求把内存中堆的另外一部分换到SWAP中去,于是在遍历堆分区的进程中,会把整个堆分区轮流往SWAP写一遍,导致GC时刻超长。线上应该限制swap区的巨细,假如swap占用份额较高应该进行排查和处理,恰当的时分能够经过下降堆巨细,或者增加物理内存。

因而,布置Java服务的Linux体系,在内存分配上要慎重。

以上内容希望能够起到抛转引玉的效果,如有了解不到位的地方烦请指出。