linux性能之内存篇
linux性能之内存篇
一、内存管理
linux内核给每一个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的。这样,进程就可以很方便地访问内存,也就是虚拟内存。
10多年的淮阳网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。成都全网营销推广的优势是能够根据用户设备显示端的尺寸不同,自动调整淮阳建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。成都创新互联从事“淮阳网站设计”,“淮阳网站推广”以来,每个客户项目都认真落实执行。
虚拟地址空间分为:内核空间和用户空间,不同字长(cpu指令可以处理数据的最大长度)的处理器,地址空间的范围也不同。如:32位和64位
进程在用户态时,只能访问用户空间的内存;只有进入内核态时才能访问内核空间内存。虽然每个进程的地址空间都包含了内核空间,但这些内核空间,其实关联的都是相同的物理内存。这样,进程切换到内核态后,就可以很方便地访问内核空间内存。
每一个进程都有一个这么大的地址空间,那么所有进程的虚拟内存加起来,自然要比实际的物理内存大的多。并不是所有的虚拟内存都会分配物理内存,只有那些实际使用的虚拟内存才分配物理内存,并且分配后的物理内存,是通过内存映射来管理的。
内存映射,其实就是将虚拟内存地址映射到物理内存地址。为了完成内存映射,内核为每一个进程都维护了一张页表,记录虚拟地址与物理地址的映射关系
页表实际存储在cpu的内存管理单元MMU中,这样,正常情况下,处理器就可以直接通过硬件,找到要访问的内存。当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。
TLB(备缓冲器),就是MMU中页表的高速缓存。由于进程的虚拟地址空间是独立的,而TLB的访问速度又比MMU快得多,所以,通过减少进程的上下文切换,减少TLB的刷新次数,就可以提高TLB缓存的使用率,进而提高cpu的内存访问性能。
MMU不是以字节为单位来管理内存,而是规定了一个内存映射的最小单位,也就是页,大小4KB。每一次内存映射,都需要关联4KB或者4KB整数倍的内存空间。
为了解决页表项过多的问题,linux提供了两种机制,也就是多级页和大页(HugePage)。
多级页:就是把内存分成区块来管理,将原来的映射关系改成块索引和区块内的偏移。由于虚拟内存空间通常只用了很少一部分,那么,多级页就保存这些使用中的区块,这样就可以大大地减少页表的项数。
linux使用四级页表来管理内存页。前4个表项用于选择页,最后一个索引表示页内偏移。
大页:比普通页更大的内存块,常见的有2MB和1GB。大页通常用在使用大量内存的进程上,如:Oracle等
二、linux进程如何使用内存
虚拟内存空间分布
1、只读段:包括代码和常量
2、数据段:包括全局变量
3、堆:包括动态分配内存,从低地址开始向上增长
4、文件映射段:包括动态库、共享内存等,从高地址开始向下增长
5、栈:包括局部变量和函数调用的上下文等。栈的大小都是固定的,一般都是8MB。
内存分配和回收
malloc()是C标准库提供的内存分配函数,对应到系统调用上,有两种实现方式:brk()和mmap()。
brk():小块内存(小于128k),C标准库使用brk()来分配,也就是通过移动堆顶的位置来分配内存。这些内存释放后并不会立即归还系统,而是被缓存起来,这样就可以重复使用。
MMap():大块内存(大于128k),则直接使用内存映射mmap()来分配,也就是在文件映射段找一块空闲内存分配出去,释放后直接归还系统。
但是,这两种分配都有优缺点,brk可以减少缺页异常的发生,提高内存访问效率。这些内存没有归还系统,在内存工作繁忙时,在内存工作繁忙时,频繁的内存分配和释放会造成内存碎片。mmap会直接归还系统,所以每次mmap都会发生缺页异常。在内存工作繁忙时,频繁的内存分配会导致大量的缺页异常,使内核的管理负担增大。
linux通过三种方式回收内存:
回收缓存:使用LRU算法,回收最近使用最少的内存页面;
回收不常访问的内存:把不常用的内存通过交换分区直接写在磁盘中;
杀死进程:内存紧张时系统会通过oom,直接杀死占用大量内存的进程
其中,第二种方式回收不常访问的内存时,会用到交换分区swap。swap其实就是把一块磁盘空间当作内存使用。它可以把进程暂时不用的数据存储到磁盘中(这个过程就是换出)。当进程访问这些内存时,再从磁盘读取这些数据到内存中(这个过程称换入)。通常在内存不足时,才会使用swap。
第三种方式oom,其实是内核的一种保护机制。它监控进程的内存使用情况,并且使用oom_score为每一个进程的内存使用情况进行评分:
一个进程消耗的内存越大,oom_score就越大;
一个进程运行占用用的cpu越多,oom_score就越小。
可以通过手动设置进程的oom_adj,从而调整进程的oom_score。oom_adj的范围【-17,15】,数值越大,表示进程越容易被oom杀死;数值越小,表示进程越不容易被oom杀死,其中-17表示禁止oom。
[root@test proc]# lsof -i:22
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sshd 3043 root 3u IPv4 21799 0t0 TCP *:ssh (LISTEN)
sshd 3499 root 3u IPv4 23448 0t0 TCP test:ssh->123.139.156.118:53251 (ESTABLISHED)
sshd 3552 root 3u IPv4 23652 0t0 TCP test:ssh->123.139.156.118:53254 (ESTABLISHED)
[root@test proc]# cat /proc/3043/oom_adj
-17
#此时ssh进程的oom_adj为-17,ssh就不容易被杀死
查看内存整体情况
free
[root@test ~]# free -hm
total used free shared buff/cache available
Mem: 991M 93M 399M 484K 498M 737M
Swap: 0B 0B 0B
[root@test ~]# grep Cached /proc/meminfo
Cached: 408672 kB
SwapCached: 0 kB
[root@test ~]# grep Buffer /proc/meminfo
Buffers: 41252 kB
total:总内存大小;
used:已经使用的内存大小,包含了共享内存;
free:未使用内存大小;
shared:共享内存大小;
buff/cache:缓存和缓冲区的大小;
available:新进程可用内存的大。
注意:available包含了可回收的缓存,所以大于free未使用的内存。并不是所有缓存都可以回收。
top
[root@test proc]# top
top - 11:42:36 up 2:10, 2 users, load average: 0.00, 0.01, 0.05
Tasks: 77 total, 1 running, 76 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.3 us, 0.3 sy, 0.0 ni, 98.0 id, 1.3 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 1015024 total, 352724 free, 94188 used, 568112 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 747996 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
3476 root 20 0 611432 13556 2364 S 0.3 1.3 0:23.47 barad_agent
3775 root 20 0 571352 7168 2528 S 0.3 0.7 0:10.34 YDService
1 root 20 0 125476 3916 2592 S 0.0 0.4 0:01.45 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
3 root 20 0 0 0 0 S 0.0 0.0 0:00.10 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 0:00.61 rcu_sched
VIRT:进程虚拟内存的大小,只要申请过的内存,即便还没有真正分配物理内存,也会计算在内。
RES:常驻内存大小,也就是进程实际使用的物理内存大小,但不包括swap和共享内存
SHR:共享内存大小,如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等。
%MEM:进程使用物理内存占系统内存的百分比。
Buffers and Cached
Buffers是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通过不会特别大(几十MB)。这样内核就可以把分散的写集中起来,统一优化磁盘写入,多次小的写合并成单次大的写。
Cached是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。这样下次访问的时候直接从内存读取。
SReclaimable是Slab的一部分。Slab包括两部分,可回收用:SReclaimable。不可回收:SUnreclaim。
注意:buffers和cache既有读又有写。不是单一的读或者写
进程缓存命中率
查看缓存命中率需要安装bcc软件包
centos7系统安装
[root@centos ~]# yum update
[root@centos ~]# rpm --import
https://www.elrepo.org/RPM-GPG-KEY-elrepo.org && rpm -Uvh
http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm
[root@centos ~]# uname -r ##
3.10.0-862.el7.x86_64
[root@centos ~]# yum remove kernel-headers kernel-tools kernel-tools-libs
[root@centos ~]# yum --disablerepo="" --enablerepo="elrepo-kernel" install
kernel-ml kernel-ml-devel kernel-ml-headers kernel-ml-tools
kernel-ml-tools-libs kernel-ml-tools-libs-devel
[root@centos ~]# sed -i '/GRUB_DEFAULT/s/=./=0/' /etc/default/grub
[root@centos ~]# grub2-mkconfig -o /boot/grub2/grub.cfg
[root@centos ~]# reboot
[root@centos ~]# uname -r ## 升级成功
4.20.0-1.el7.elrepo.x86_64
[root@centos ~]# yum install -y bcc-tools
[root@centos ~]# echo 'export PATH=$PATH:/usr/share/bcc/tools' > /etc/profile.d/bcc-tools.sh
[root@centos ~]# . /etc/profile.d/bcc-tools.sh
[root@centos ~]# cachestat 1 1 ## 测试安装是否成功Ubuntu系统安装步骤
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4052245BD4284CDD echo "deb https://repo.iovisor.org/apt/xenial xenial main" | sudo tee /etc/apt/sources.list.d/iovisor.list sudo apt-get update sudo apt-get install -y bcc-tools libbcc-examples linux-headers-$(uname -r) #bcc安装到/usr/share/bcc/tools这个目录中。需要配置系统的path路径 export PATH=$PATH:/usr/share/bcc/tools
注意:bcc软件包,必须是内核4.1以上。
cachestat/cachetop进程的缓存命中
root@VM-16-7-ubuntu:~# cachestat 1 3
HITS MISSES DIRTIES HITRATIO BUFFERS_MB CACHED_MB
3414 0 5 100.00% 32 563
59 0 4 100.00% 32 563
62 0 4 100.00% 32 563
root@VM-16-7-ubuntu:~# cachetop
12:50:16 Buffers MB: 80 / Cached MB: 572 / Sort: HITS / Order: ascending
PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT%
3267 root YDService 2 0 0 100.0% 0.0%
1843 root barad_agent 3 0 1 66.7% 0.0%
20042 root barad_agent 8 0 3 62.5% 0.0%
323 root jbd2/vda1-8 8 8 6 12.5% 12.5%
20041 root barad_agent 9 2 4 45.5% 9.1%
20034 root cachetop 30 0 0 100.0% 0.0%
20044 root sh 158 0 0 100.0% 0.0%
20045 root sh 158 0 0 100.0% 0.0%
20046 root sh 158 0 0 100.0% 0.0%
20043 root sh 392 0 0 100.0% 0.0%
20044 root cat 491 0 0 100.0% 0.0%
20043 root barad_agent 496 0 0 100.0% 0.0%
20045 root grep 638 0 0 100.0% 0.0%
20046 root awk 915 0 0 100.0% 0.0%
#指标
TOTAL:总的IO次数;
MISSES:缓存未命中的次数;
HITS:缓存命中次数;
DIRTIES:新增到缓存中的脏页数;
CACHED_MB:buffer的大小,MB为单位;
BUFFERS_MB:cache的大小,MB为单位;
pcstat文件缓存查看
#pcstat 安装:
if [ $(uname -m) == "x86_64" ] ; then
curl -L -o pcstat https://github.com/tobert/pcstat/raw/2014-05-02-01/pcstat.x86_64
else
curl -L -o pcstat https://github.com/tobert/pcstat/raw/2014-05-02-01/pcstat.x86_32
fi
chmod 755 pcstat
./pcstat #即可使用
####
root@VM-16-7-ubuntu:~# ls
pcstat
root@VM-16-7-ubuntu:~# ./pcstat pcstat
|----------+----------------+------------+-----------+---------|
| Name | Size | Pages | Cached | Percent |
|----------+----------------+------------+-----------+---------|
| pcstat | 3049296 | 745 | 745 | 100.000 |
|----------+----------------+------------+-----------+---------|
##指标
三、内存泄漏
当进程通过malloc()申请虚拟内存后,系统并不会立即为其分配物理内存,而是在首次访问时,才通过缺页异常陷入内核中分配内存。为了协调快速cpu和慢速磁盘的性能差异,linux还会使用cache和buffer,分别把文件和磁盘读写的数据缓存到内存中。所以,对于程序来说,动态分配内存和回收,就是事故的地点。
没有正确回收分配后的内存,导致了泄漏。
访问的是已分配内存边界外的地址,导致程序异常退出等等
内存的分配和回收
进程的内存空间
1、只读段:包括代码和常量。不会再去分配新的内存,所以不会产生内存泄漏
2、数据段:包括全局变量。变量在定义的时候就确定了大小,所以不会产生内存泄漏
3、堆:包括动态分配内存,从低地址开始向上增长。应用程分配管理,没有正确释放堆内存,内存泄漏
4、文件映射段:包括动态库、共享内存等,从高地址开始向下增长。共享内存也是程序管理,内存泄漏
5、栈:包括局部变量和函数调用的上下文等。栈的大小都是固定的,一般都是8MB。系统管理,不会泄漏
四、内存泄漏案例
环境
2cpu 8GB内存
预先安装sysstat docker bcc
#安装docker、sysstat
sudo apt-get install -y sysstat docker.io
#安装bcc
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4052245BD4284CDD
echo "deb https://repo.iovisor.org/apt/bionic bionic main" | sudo tee /etc/apt/sources.list.d/iovisor.list
sudo apt-get update
sudo apt-get install -y bcc-tools libbcc-examples linux-headers-$(uname -r)
#拉取镜像
docker run --name=app -itd feisk /app:mem-leak #k后面加y
#检查
root@test:~# docker logs app
2th => 1
3th => 2
4th => 3
5th => 5
6th => 8
7th => 13
8th => 21
9th => 34
10th => 55
排查
vmstat
root@test:~# vmstat 3
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 143036 108584 558084 0 0 20 63 146 313 1 0 99 0 0
0 0 0 142904 108584 558068 0 0 0 0 173 400 0 0 100 0 0
0 0 0 142440 108588 558072 0 0 0 271 159 333 1 1 96 2 0
0 0 0 142472 108588 558072 0 0 0 17 128 313 1 0 99 0 0
1 0 0 142472 108588 558072 0 0 0 5 115 283 0 0 100 0 0
....
....
0 0 0 142412 108596 558076 0 0 0 29 297 708 1 1 98 0 0
0 0 0 141480 108628 558152 0 0 0 12 170 404 0 0 99 0 0
0 0 0 141512 108628 558152 0 0 0 4 172 390 0 0 100 0 0
0 0 0 141512 108628 558152 0 0 0 16 176 399 1 1 98 0 0
#观察一段时间,发现free不断减少,buffer和cache基本不变。说明系统内存一直在使用,但是,无法说明内存泄漏
memleak
bcc软件包中的
root@test:~# /usr/share/bcc/tools/memleak -a -p 17050
Attaching to pid 17050, Ctrl+C to quit.
cannot attach uprobe, Device or resource busy
[19:26:43] Top 10 stacks with outstanding allocations:
addr = 7f389c2fc700 size = 8192
addr = 7f389c2f86e0 size = 8192
addr = 7f389c300720 size = 8192
addr = 7f389c2fa6f0 size = 8192
addr = 7f389c2fe710 size = 8192
40960 bytes in 5 allocations from stack
fibonacci+0x1f [app]
child+0x4f [app]
start_thread+0xdb [libpthread-2.27.so]
[19:26:48] Top 10 stacks with outstanding allocations:
# -a 表示显示每个内存分配请求的大小以及地址
# -p 指定案例应用的 PID 号
# /usr/share/bcc/tools/
# -a 表示显示每个内存分配请求的大小以及地址
# -p 指定案例应用的 PID 号
#app进程一直在分配内存,并且fibonacci()函数分配的内存没有释放。
=====
#检查代码
root@test:~# docker exec app cat /app.c
#include
#include
#include
#include
long long *fibonacci(long long *n0, long long *n1)
{
long long *v = (long long *) calloc(1024, sizeof(long long));
*v = *n0 + *n1;
return v;
}
void *child(void *arg)
{
long long n0 = 0;
long long n1 = 1;
long long *v = NULL;
for (int n = 2; n > 0; n++) {
v = fibonacci(&n0, &n1);
n0 = n1;
n1 = *v;
printf("%dth => %lld\n", n, *v);
sleep(1);
}
}
int main(void)
{
pthread_t tid;
pthread_create(&tid, NULL, child, NULL);
pthread_join(tid, NULL);
printf("main thread exit\n");
return 0;
}root@test:~#
#发现child()调用了fibonacci函数,但是并没有释放fibonacci返回的内存。在child函数加free(v);释放内存
新闻名称:linux性能之内存篇
路径分享:http://cdiso.cn/article/jgcego.html