简介
swap是磁盘上一块存储空间。当系统内存使用超过一定值的时候,操作系统就会启动内核进程kswapd,kswapd将部分内存数据置换到swap,从而释放一部分内存出来。swap让进程可以使用超过物理内存大小的内存空间。由于swap是磁盘上的一块空间,所以其读写性能和内存差了1000~10000倍。
功能
如果没有swap的存在,那么一旦进程使用超过物理内存大小的空间,进程就会被oom机制(操作系统为了保证自己能正常运行,强制结束应用进程)强制结束。有了swap的存在,运维人员可以监控swap的使用情况,然后有计划的调整服务器内存。如果开发人员写的代码存在内存泄漏的问题,而被泄漏的内存很可能长时间不被访问,有很大概率被置换到swap,所以swap可以延缓内存泄漏带来的内存耗尽问题。
原理
操作系统将内存分为不同的zone,每个zone管理一片内存区域,/proc/zoneinfo有各个zone管理的内存块信息,每个zone的信息里面有pages free,low,high,min,pages free是zone的空闲页(4KB)数量,low,high,min是三个水位线(见下图)。当某个zone的pages free低于low,kswapd进程就会被唤醒,kswapd扫描内存并将部分内存数据置换到swap(简称为swap out),导致pages free增加;当pages free高于high,内核进程kswapd进入睡眠状态,停止swap out。被swap out到磁盘上的swap块上面的内存数据可能需要被应用程序访问,数据又会被读取到内存(简称为swap in)。通过命令vmstat可以监控到系统的swap out和swap in行为。min的值就是当某个zone的pages free低于min的时候,就会触发oom(为了避免操作系统崩溃,强制结束应用进程),大致过程如下图。
常用命令
free -m #查看swap大小
vmstat -w -t 1 #观察so,si列(swap out,swap in),当这两列
#持续不为0,意味着内存不够用了,需要排查应用
#是否存在泄漏,是否需要扩服务器内存
sar -B 1 #如果pgscank大于0,那说明kswapd在工作
#如果pgsteal不为0,说明有内存被回收
cat /proc/zoneinfo
cat /proc/meminfo
cat /proc/[pid]/status #VmSwap代表进程有多少内存在磁盘swap分区上
#redhat8创建swap
lvcreate -L 8G -n swaplv2 rootvg #创建lv
mkswap /dev/rootvg/swaplv2 #创建swap
swapon /dev/rootvg/swaplv2 #激活swap
free -g #查看free
测试
写个程序分配内存,再写个程序监控zone的pages使用情况
//sbrk.c
//通过系统调用sbrk获取内存
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
//操作系统按页管理内存,每个页4KB
#define PG 4096
//获取n个页,需要对获取的内存写点数据,否则
//无法实际占用物理内存
void getm(int n){
void *ds=sbrk(n*PG);
if(ds==(void*)-1){
printf("Out of Memory\n");
return 0;
}
void *end=ds+n*PG;
for(;;ds+=PG){
if(ds>=end) break;
*(int *)(ds)=1;
}
}
int num=0;
int main(int argc,char *argv[]){
int def=5000; //默认每秒分配5000个页
if(argc == 2){
def=atoi(argv[1]); //可以传递一个参数设置每秒分配多少页
}
while(1){
getm(def);
num+=def;
printf("%d\n",num);
sleep(1);
}
return 0;
}
#!/usr/bin/python3
#通过/proc/zoneinfo观察zone内存状态
import re
import datetime
import sys
import time
def getnamelen():
global namelen
namelen=0
fname='/proc/zoneinfo'
f=open(fname,'r')
fcontent=f.read()
pattern='^Node.*'
zonelist=re.findall(pattern,fcontent,flags=re.M)
namelen=len(zonelist[0])
f.close()
def pzoneinfo():
ISOTIMEFORMAT='%m-%d %H:%M:%S'
times=datetime.datetime.now().strftime(ISOTIMEFORMAT)
fname='/proc/zoneinfo'
f=open(fname,'r')
fcontent=f.read()
#通过正则表达式提取/proc/zoneinfo里面的zone name,pages free,min,high,low
pattern='^Node.*'
zonelist=re.findall(pattern,fcontent,flags=re.M)
pattern='^ min.*?(\d+)'
min=re.findall(pattern,fcontent,flags=re.M)
pattern='^ high.*?(\d+)'
high=re.findall(pattern,fcontent,flags=re.M)
pattern='^ low.*?(\d+)'
low=re.findall(pattern,fcontent,flags=re.M)
pattern='^ pages free.*?(\d+)'
free=re.findall(pattern,fcontent,flags=re.M)
print("{:^{namelen}} {:<10} {:<10} {:<10} {:<10} {:<10}".format("zonename","free","high","low","min","time",namelen=namelen))
zonen=0
for z in zonelist:
print("{} {:<10} {:<10} {:<10} {:<10} {}".format(z,free[zonen],high[zonen],low[zonen],min[zonen],times))
zonen=zonen+1
totalfree=0
for f in free:
totalfree+=int(f)
print("{:^{namelen}} {:<10}".format("total free",str(totalfree),namelen=namelen))
if __name__=='__main__':
getnamelen()
if len(sys.argv)==1:
print(namelen)
pzoneinfo()
elif len(sys.argv)==2:
ss=int(sys.argv[1])
while(1):
pzoneinfo()
time.sleep(ss)
执行程序
gcc sbrk.c -o sbrk
./sbrk 10000 #根据自己测试服务内存大小调整内存获取速度
python3 watchzone.py #开新的窗口执行
vmstat -w -t 1 #开新的窗口执行
sar -B 1 #开新的窗口执行
当vmstat -w -t 1观察到si so持续不为0就可以中断各个窗口程序了
- 从vmwat.png可以观察到从11:19:43开始出现so,直接定位watchzone.png的11:19:43左右的数据
- 从watchzone.png可以看到11:19:42之前total free是每秒减少10000,但是11:19:43的时候total free只减少了4000,猜测是kswapd回收了近6000个页(将6000和vmstat的so对比,存在一定误差,可以接受)
- 对比free列和low列的值,发现11:19:42的free非常接近low,分配10000个页必然触发低于low
swappiness
除了少部分特殊用途的内存,剩余的内存可以分为两种用途:缓存文件,动态分配(malloc函数);swappiness的作用是调整kswapd回收内存时候是偏向回收缓存文件的内存还是动态分配的内存。文件服务器建议设置较大的swappiness,而不太依赖文件读写的建议设置较小的swappiness。