Linux內存是怎麼工作的
通過虛擬地址訪問物理地址
首先我們8g的內存,其實指的是物理內存,也就是主存,
在Linux系統裏邊,我們的進程是不能直接訪問物理內存的,它會爲每個進程分配一個虛擬地址內存,然後通過虛擬地址內存去訪問,
在CPU的內存管理單元裏邊有一個頁表,它會把我們的虛擬內存跟物理內存之間做一個映射,
只有那些實際使用的虛擬內存纔會分配物理內存,也就是提供一個內存映射的關係
當進程訪問的虛擬地址在列表中查不到的時候,就會產生一個缺頁異常,然後就會重新分配物理內存,然後更新頁表,再返回給用戶空間,這樣進程就恢復正常
內存映射的最小單位是頁,這個頁一般是比較小的,一般是4kb左右,
它頁比較小,就有一個問題,因爲這個頁比較小,所以需要比較多的頁才能提供這個內存映射的關係,所以整個頁表會變得非常大,
那麼裏面爲了解決這個頁表變得非常大的問題,Linux有兩種機制,一個是多級頁表,一個是大頁,
Linux的多級頁表採用的是4級頁表,它通過將原來的映射關係,改成區塊索引以及區塊內的偏移,
因爲虛擬內存空間通常只用到了很少的一部分,多級頁表就只保存這些用到的虛擬內存,這樣就可以減少那個頁表的數量,
還有一種機制是大頁,也就是分配比一般的頁更大的頁,常見的有2MB,1GB,大頁通常用在那些使用大量內存的進程上,比如 Oracle,還有es,logstash他們佔用的內存也挺多的,
內存的分配與回收
malloc() 是 C 標準庫提供的內存分配函數,對應到系統調用上,有兩種實現方式,即 brk() 和 mmap()。
對小塊內存(小於 128K),C 標準庫使用 brk() 來分配,也就是通過移動堆頂的位置來分配內存。這些內存釋放後並不會立刻歸還系統,而是被緩存起來,這樣就可以重複使用。
而大塊內存(大於 128K),則直接使用內存映射 mmap() 來分配,也就是在文件映射段找一塊空閒內存分配出去。
brk() 方式的緩存,釋放後不會立即歸還給系統,可以減少缺頁異常的發生,提高內存訪問效率。當時,因爲這些內存沒有歸還系統,所以在內存工作繁忙時,頻繁的內存分配和釋放會造成內存碎片。
而 mmap() 方式分配的內存,會在釋放時直接歸還系統,所以每次 mmap 都會發生缺頁異常。在內存工作繁忙時,頻繁的內存分配會導致大量的缺頁異常,使內核的管理負擔增大。這也是malloc 只對大塊內存使用 mmap 的原因
。
對內存來說,如果只分配而不釋放,就會造成內存泄漏,甚至會耗盡系統內存。所以,在應用程序用完內存後,還需要調用 free() 或 unmap() ,來釋放這些不用的內存
在發現內存緊張時,系統也有一系列機制來回收內存,
- 回收緩存,使用
LRU
(least Recently Used)算法,對那些最近最少使用的的內存頁面進行回收 - 將那些不常訪問的內存放到
swap交換分區
- OOM機制(out of memory),直接殺掉佔用大量內存的進程。
Linux的swap機制
Swap 把這些不常訪問的內存先寫到磁盤中,然後釋放這些內存,給其他更需要的進程使用。再次訪問這些不常訪問的內存的時時候,重新從磁盤讀入內存就可以了。
簡單來說,Swap 說白了就是把一塊磁盤空間或者一個本地文件(以下講解以磁盤爲例),當成內存來使用。它包括換出和換入兩個過程
- 換出就是把進程暫時沒有用到的不常用的內存放到磁盤裏面
- 換入就是當進程想再次訪問這些內存的時候,再把它從磁盤換到內存
swap它把我們系統的可用內存變大了,但是因爲硬盤比內存慢得多,所以如果swap被大量使用了,就要進一步排查。
平時我們筆記本電腦休眠的時候,利用的就是swap的原理,休眠時,把系統的內存存入磁盤,這樣等到再次開機時,只要從磁盤中加載內存就可以。這樣就省去了很多應用程序的初始化過程,加快了開機速度。
swap是用來回收內存的,對於回收內存,最容易想到的場景就是有一個新的大塊內存需要分配,但是現在內存不夠,這時候就需要回收一部分內存,它被稱爲直接內存回收
另外,還有一個專門的內核線程用來定期回收內存,叫kswapd0。kswapd0 定義了三個內存閾值(watermark,也稱爲水位),分別是
頁最小閾值(pages_min)、頁低閾值(pages_low)和頁高閾值(pages_high)。剩餘內存,則使用 pages_free 表示。
- 剩餘內存小於頁最小閾值,說明進程可用內存都耗盡了,只有內核纔可以分配內存
- 剩餘內存落在頁最小閾值和頁低閾值中間,說明內存壓力比較大,這時 kswapd0 會執行內存回收,直到剩餘內存大於高閾值爲止。
另外,也有可能出現可用內存還有很多,但是swap被使用得也很多。可能是因爲一些進程的內存佔用比較大,所以佔用了swap內存,但是當這個進程結束的時候,它的可用內存就恢復得比較大了。但是由於之前他佔用了交換分區,交換分區裏面的內存還沒有放到可用內存裏邊,還是放在磁盤裏邊的,所以就會造成這種情況發生。
查看內存
free
[root@VM_0_11_centos ~]# free
total used free shared buff/cache available
Mem: 1882056 385240 78224 624 1418592 1308716
Swap: 0 0 0
最後一列的可用內存 available 。available 不僅包含未使用內存,還包括了可回收的緩存。但是,並不是所有緩存都可以回收,因爲有些緩存可能正在使用中。
free 顯示的是整個系統的內存使用情況。如果你想查看進程的內存使用情況,使用 top
[root@VM_0_11_centos ~]# top
top - 20:22:42 up 25 days, 5:36, 1 user, load average: 0.00, 0.01, 0.05
Tasks: 90 total, 2 running, 88 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 1882056 total, 74196 free, 385740 used, 1422120 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 1308224 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 191040 2852 1408 S 0.0 0.2 4:43.55 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.02 kthreadd
4 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/0:0H
6 root 20 0 0 0 0 S 0.0 0.0 0:32.51 ksoftirqd/0
- VIRT 是進程虛擬內存的大小,只要是進程申請過的內存,即便還沒有真正分配物理內存,也會計算在內。
- RES 是常駐內存的大小,也就是進程實際使用的物理內存大小,但不包括 Swap 和共享內存。
- SHR 是共享內存的大小,比如與其他進程共同使用的共享內存、加載的動態鏈接庫以及程序的代碼段等。
- %MEM 是進程使用物理內存佔系統總內存的百分比。
如何理解cache和buffer
在free命令裏面其他指標都很好理解,然後有個buffer和cache,它的字面意識是buffer是緩衝,cache是緩存,之前我man free去查看過他的定義,
buffers
Memory used by kernel buffers (Buffers in /proc/meminfo)
cache Memory used by the page cache and slabs (Cached and SReclaimable in /proc/meminfo)
buff/cache
Sum of buffers and cache
他的幫助手冊裏面顯示:
- buffer是用於內核緩衝區的內存大小
- cache是用於頁緩存和slabs的內存大小
它的幫助手冊裏面告訴我們,這些數值都來自 /proc/meminfo
。實際上,很多的性能分析工具,他們的數據都是從/proc/這個目錄裏面獲取的。所以我們可以繼續man
一下proc目錄,
它這裏的定義是
- buffer是對原始磁盤數據塊的臨時存儲,也就是緩存
磁盤的數據
,通常只有20兆左右,它是用來優化數據寫入磁盤的,可以把多次分散的寫集中起來,統一成一次來寫入,。 - Cached 是從磁盤讀取文件的頁緩存,也就是用來
緩存從文件讀取的數據
。這樣,下次訪問這些文件數據時,就可以直接從內存中快速獲取,而不需要再次訪問緩慢的磁盤。
Buffer 的文檔沒有提到這是磁盤讀數據還是寫數據的緩存,而在很多網上資料裏面,都會提到 Buffer 只是對將要寫入磁盤數據的緩存
文檔中提到,Cache 是對從文件讀取數據的緩存,那麼它是不是也會緩存寫文件的數據呢?
我之前使用dd
命令分別進行磁盤和文件的寫和讀的實驗,然後開另一個終端用vmstat來觀察,發現:
- Buffer 既可以用作“將要寫入磁盤數據的緩存”,也可以用作“從磁盤讀取數據的緩存”。
- Cache 既可以用作“從文件讀取數據的頁緩存”,也可以用作“寫文件的頁緩存”。
簡單來說,Buffer 是對磁盤數據的緩存,而 Cache 是文件數據的緩存,它們既會用在讀請求中,也會用在寫請求中。