您好,登錄后才能下訂單哦!
? linux內(nèi)核給每一個進(jìn)程都提供了一個獨(dú)立的虛擬地址空間,并且這個地址空間是連續(xù)的。這樣,進(jìn)程就可以很方便地訪問內(nèi)存,也就是虛擬內(nèi)存。
? 虛擬地址空間分為:內(nèi)核空間和用戶空間,不同字長(cpu指令可以處理數(shù)據(jù)的最大長度)的處理器,地址空間的范圍也不同。如:32位和64位
? 進(jìn)程在用戶態(tài)時,只能訪問用戶空間的內(nèi)存;只有進(jìn)入內(nèi)核態(tài)時才能訪問內(nèi)核空間內(nèi)存。雖然每個進(jìn)程的地址空間都包含了內(nèi)核空間,但這些內(nèi)核空間,其實(shí)關(guān)聯(lián)的都是相同的物理內(nèi)存。這樣,進(jìn)程切換到內(nèi)核態(tài)后,就可以很方便地訪問內(nèi)核空間內(nèi)存。
? 每一個進(jìn)程都有一個這么大的地址空間,那么所有進(jìn)程的虛擬內(nèi)存加起來,自然要比實(shí)際的物理內(nèi)存大的多。并不是所有的虛擬內(nèi)存都會分配物理內(nèi)存,只有那些實(shí)際使用的虛擬內(nèi)存才分配物理內(nèi)存,并且分配后的物理內(nèi)存,是通過內(nèi)存映射來管理的。
? 內(nèi)存映射,其實(shí)就是將虛擬內(nèi)存地址映射到物理內(nèi)存地址。為了完成內(nèi)存映射,內(nèi)核為每一個進(jìn)程都維護(hù)了一張頁表,記錄虛擬地址與物理地址的映射關(guān)系
? 頁表實(shí)際存儲在cpu的內(nèi)存管理單元MMU中,這樣,正常情況下,處理器就可以直接通過硬件,找到要訪問的內(nèi)存。當(dāng)進(jìn)程訪問的虛擬地址在頁表中查不到時,系統(tǒng)會產(chǎn)生一個缺頁異常,進(jìn)入內(nèi)核空間分配物理內(nèi)存、更新進(jìn)程頁表,最后再返回用戶空間,恢復(fù)進(jìn)程的運(yùn)行。
? TLB(備緩沖器),就是MMU中頁表的高速緩存。由于進(jìn)程的虛擬地址空間是獨(dú)立的,而TLB的訪問速度又比MMU快得多,所以,通過減少進(jìn)程的上下文切換,減少TLB的刷新次數(shù),就可以提高TLB緩存的使用率,進(jìn)而提高cpu的內(nèi)存訪問性能。
? MMU不是以字節(jié)為單位來管理內(nèi)存,而是規(guī)定了一個內(nèi)存映射的最小單位,也就是頁,大小4KB。每一次內(nèi)存映射,都需要關(guān)聯(lián)4KB或者4KB整數(shù)倍的內(nèi)存空間。
? 為了解決頁表項(xiàng)過多的問題,linux提供了兩種機(jī)制,也就是多級頁和大頁(HugePage)。
? 多級頁:就是把內(nèi)存分成區(qū)塊來管理,將原來的映射關(guān)系改成塊索引和區(qū)塊內(nèi)的偏移。由于虛擬內(nèi)存空間通常只用了很少一部分,那么,多級頁就保存這些使用中的區(qū)塊,這樣就可以大大地減少頁表的項(xiàng)數(shù)。
? linux使用四級頁表來管理內(nèi)存頁。前4個表項(xiàng)用于選擇頁,最后一個索引表示頁內(nèi)偏移。
? 大頁:比普通頁更大的內(nèi)存塊,常見的有2MB和1GB。大頁通常用在使用大量內(nèi)存的進(jìn)程上,如:Oracle等
虛擬內(nèi)存空間分布
1、只讀段:包括代碼和常量
2、數(shù)據(jù)段:包括全局變量
3、堆:包括動態(tài)分配內(nèi)存,從低地址開始向上增長
4、文件映射段:包括動態(tài)庫、共享內(nèi)存等,從高地址開始向下增長
5、棧:包括局部變量和函數(shù)調(diào)用的上下文等。棧的大小都是固定的,一般都是8MB。
內(nèi)存分配和回收
? malloc()是C標(biāo)準(zhǔn)庫提供的內(nèi)存分配函數(shù),對應(yīng)到系統(tǒng)調(diào)用上,有兩種實(shí)現(xiàn)方式:brk()和mmap()。
? brk():小塊內(nèi)存(小于128k),C標(biāo)準(zhǔn)庫使用brk()來分配,也就是通過移動堆頂?shù)奈恢脕矸峙鋬?nèi)存。這些內(nèi)存釋放后并不會立即歸還系統(tǒng),而是被緩存起來,這樣就可以重復(fù)使用。
? MMap():大塊內(nèi)存(大于128k),則直接使用內(nèi)存映射mmap()來分配,也就是在文件映射段找一塊空閑內(nèi)存分配出去,釋放后直接歸還系統(tǒng)。
? 但是,這兩種分配都有優(yōu)缺點(diǎn),brk可以減少缺頁異常的發(fā)生,提高內(nèi)存訪問效率。這些內(nèi)存沒有歸還系統(tǒng),在內(nèi)存工作繁忙時,在內(nèi)存工作繁忙時,頻繁的內(nèi)存分配和釋放會造成內(nèi)存碎片。mmap會直接歸還系統(tǒng),所以每次mmap都會發(fā)生缺頁異常。在內(nèi)存工作繁忙時,頻繁的內(nèi)存分配會導(dǎo)致大量的缺頁異常,使內(nèi)核的管理負(fù)擔(dān)增大。
? linux通過三種方式回收內(nèi)存:
回收緩存:使用LRU算法,回收最近使用最少的內(nèi)存頁面;
回收不常訪問的內(nèi)存:把不常用的內(nèi)存通過交換分區(qū)直接寫在磁盤中;
殺死進(jìn)程:內(nèi)存緊張時系統(tǒng)會通過oom,直接殺死占用大量內(nèi)存的進(jìn)程
其中,第二種方式回收不常訪問的內(nèi)存時,會用到交換分區(qū)swap。swap其實(shí)就是把一塊磁盤空間當(dāng)作內(nèi)存使用。它可以把進(jìn)程暫時不用的數(shù)據(jù)存儲到磁盤中(這個過程就是換出)。當(dāng)進(jìn)程訪問這些內(nèi)存時,再從磁盤讀取這些數(shù)據(jù)到內(nèi)存中(這個過程稱換入)。通常在內(nèi)存不足時,才會使用swap。
第三種方式oom,其實(shí)是內(nèi)核的一種保護(hù)機(jī)制。它監(jiān)控進(jìn)程的內(nèi)存使用情況,并且使用oom_score為每一個進(jìn)程的內(nèi)存使用情況進(jìn)行評分:
一個進(jìn)程消耗的內(nèi)存越大,oom_score就越大;
一個進(jìn)程運(yùn)行占用用的cpu越多,oom_score就越小。
? 可以通過手動設(shè)置進(jìn)程的oom_adj,從而調(diào)整進(jìn)程的oom_score。oom_adj的范圍【-17,15】,數(shù)值越大,表示進(jìn)程越容易被oom殺死;數(shù)值越小,表示進(jìn)程越不容易被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進(jìn)程的oom_adj為-17,ssh就不容易被殺死
查看內(nèi)存整體情況
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:總內(nèi)存大?。?/p>
used:已經(jīng)使用的內(nèi)存大小,包含了共享內(nèi)存;
free:未使用內(nèi)存大?。?/p>
shared:共享內(nèi)存大?。?/p>
buff/cache:緩存和緩沖區(qū)的大?。?/p>
available:新進(jìn)程可用內(nèi)存的大。
注意:available包含了可回收的緩存,所以大于free未使用的內(nèi)存。并不是所有緩存都可以回收。
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:進(jìn)程虛擬內(nèi)存的大小,只要申請過的內(nèi)存,即便還沒有真正分配物理內(nèi)存,也會計(jì)算在內(nèi)。
RES:常駐內(nèi)存大小,也就是進(jìn)程實(shí)際使用的物理內(nèi)存大小,但不包括swap和共享內(nèi)存
SHR:共享內(nèi)存大小,如與其他進(jìn)程共同使用的共享內(nèi)存、加載的動態(tài)鏈接庫以及程序的代碼段等。
%MEM:進(jìn)程使用物理內(nèi)存占系統(tǒng)內(nèi)存的百分比。
Buffers and Cached
Buffers是對原始磁盤塊的臨時存儲,也就是用來緩存磁盤的數(shù)據(jù),通過不會特別大(幾十MB)。這樣內(nèi)核就可以把分散的寫集中起來,統(tǒng)一優(yōu)化磁盤寫入,多次小的寫合并成單次大的寫。
Cached是從磁盤讀取文件的頁緩存,也就是用來緩存從文件讀取的數(shù)據(jù)。這樣下次訪問的時候直接從內(nèi)存讀取。
SReclaimable是Slab的一部分。Slab包括兩部分,可回收用:SReclaimable。不可回收:SUnreclaim。
注意:buffers和cache既有讀又有寫。不是單一的讀或者寫
進(jìn)程緩存命中率
查看緩存命中率需要安裝bcc軟件包
centos7系統(tǒng)安裝
[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系統(tǒng)安裝步驟
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這個目錄中。需要配置系統(tǒng)的path路徑 export PATH=$PATH:/usr/share/bcc/tools
注意:bcc軟件包,必須是內(nèi)核4.1以上。
cachestat/cachetop進(jìn)程的緩存命中
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%
#指標(biāo)
TOTAL:總的IO次數(shù);
MISSES:緩存未命中的次數(shù);
HITS:緩存命中次數(shù);
DIRTIES:新增到緩存中的臟頁數(shù);
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 |
|----------+----------------+------------+-----------+---------|
##指標(biāo)
? 當(dāng)進(jìn)程通過malloc()申請?zhí)摂M內(nèi)存后,系統(tǒng)并不會立即為其分配物理內(nèi)存,而是在首次訪問時,才通過缺頁異常陷入內(nèi)核中分配內(nèi)存。為了協(xié)調(diào)快速cpu和慢速磁盤的性能差異,linux還會使用cache和buffer,分別把文件和磁盤讀寫的數(shù)據(jù)緩存到內(nèi)存中。所以,對于程序來說,動態(tài)分配內(nèi)存和回收,就是事故的地點(diǎn)。
沒有正確回收分配后的內(nèi)存,導(dǎo)致了泄漏。
訪問的是已分配內(nèi)存邊界外的地址,導(dǎo)致程序異常退出等等
內(nèi)存的分配和回收
進(jìn)程的內(nèi)存空間
1、只讀段:包括代碼和常量。不會再去分配新的內(nèi)存,所以不會產(chǎn)生內(nèi)存泄漏
2、數(shù)據(jù)段:包括全局變量。變量在定義的時候就確定了大小,所以不會產(chǎn)生內(nèi)存泄漏
3、堆:包括動態(tài)分配內(nèi)存,從低地址開始向上增長。應(yīng)用程分配管理,沒有正確釋放堆內(nèi)存,內(nèi)存泄漏
4、文件映射段:包括動態(tài)庫、共享內(nèi)存等,從高地址開始向下增長。共享內(nèi)存也是程序管理,內(nèi)存泄漏
5、棧:包括局部變量和函數(shù)調(diào)用的上下文等。棧的大小都是固定的,一般都是8MB。系統(tǒng)管理,不會泄漏
環(huán)境
2cpu 8GB內(nèi)存
預(yù)先安裝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
#觀察一段時間,發(fā)現(xiàn)free不斷減少,buffer和cache基本不變。說明系統(tǒng)內(nèi)存一直在使用,但是,無法說明內(nèi)存泄漏
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 表示顯示每個內(nèi)存分配請求的大小以及地址
# -p 指定案例應(yīng)用的 PID 號
# /usr/share/bcc/tools/
# -a 表示顯示每個內(nèi)存分配請求的大小以及地址
# -p 指定案例應(yīng)用的 PID 號
#app進(jìn)程一直在分配內(nèi)存,并且fibonacci()函數(shù)分配的內(nèi)存沒有釋放。
=====
#檢查代碼
root@test:~# docker exec app cat /app.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
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:~#
#發(fā)現(xiàn)child()調(diào)用了fibonacci函數(shù),但是并沒有釋放fibonacci返回的內(nèi)存。在child函數(shù)加free(v);釋放內(nèi)存
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。