一文吃透Linux虛擬內存:原理、機制與優化
在當今的數字化時代,計算機早已成為我們生活和工作中不可或缺的伙伴。無論是日常辦公處理文檔,還是運行大型游戲享受沉浸式娛樂體驗,亦或是企業服務器高效處理海量數據,計算機都需要迅速且穩定地對數據進行存取。而在這背后,Linux 虛擬內存技術扮演著至關重要的角色。它就像一位神奇的管家,巧妙地協調著物理內存與硬盤空間,為計算機系統的高效運轉提供堅實保障。
你是否好奇,為何計算機能同時運行多個看似內存需求巨大的程序,卻不會因內存不足而頻繁卡頓?Linux 虛擬內存究竟是如何在幕后默默施展它的 “魔法”,實現內存的高效管理和利用的?接下來,就讓我們一同深入 Linux 虛擬內存的奇妙世界,從原理到機制,再到優化策略,全方位吃透這一關鍵技術,探尋它為計算機性能提升帶來的無限可能 。
一、虛擬內存技術
1.1為什么需要使用虛擬內存
進程需要使用的代碼和數據都放在內存中,比放在外存中要快很多。問題是內存空間太小了,不能滿足進程的需求,而且現在都是多進程,情況更加糟糕。所以提出了虛擬內存,使得每個進程用于3G的獨立用戶內存空間和共享的1G內核內存空間。(每個進程都有自己的頁表,才使得3G用戶空間的獨立)這樣進程運行的速度必然很快了。而且虛擬內存機制還解決了內存碎片和內存不連續的問題。為什么可以在有限的物理內存上達到這樣的效果呢?
1.1虛擬內存概述
在深入探討 Linux 虛擬內存之前,先來明晰虛擬內存的基本概念。虛擬內存,簡單來說,是一種內存管理技術,它為每個進程提供了一個獨立的、連續的地址空間,讓進程誤以為自己擁有一塊完整且足夠大的內存空間 ,而無需關心實際物理內存的具體布局和大小限制。這就好比你擁有一個超大的虛擬倉庫,你可以隨意規劃貨物的擺放位置,而不用擔心倉庫空間不夠。
虛擬內存的主要作用之一是實現內存地址轉換。在 Linux 系統中,每個進程都有自己的虛擬地址空間,這個空間通過頁表(Page Table)與物理內存進行映射。頁表就像是一本地址翻譯字典,負責將進程使用的虛擬地址翻譯成實際的物理地址。
當程序運行時,它所訪問的內存地址都是虛擬地址。例如,當程序需要讀取某個變量的值時,它會給出一個虛擬地址。CPU 首先會根據這個虛擬地址中的頁號(Page Number)在頁表中查找對應的物理頁框號(Page Frame Number)。如果頁表中存在這個映射關系(即頁表項有效),CPU 就可以通過物理頁框號和虛擬地址中的頁內偏移(Offset)計算出實際的物理地址,從而訪問到物理內存中的數據。
但如果頁表中沒有找到對應的映射關系(即發生缺頁異常,Page Fault),系統會認為這個虛擬頁還沒有被加載到物理內存中。此時,操作系統會介入,從磁盤的交換區(Swap Area)或者文件系統中找到對應的物理頁,并將其加載到物理內存中,同時更新頁表,建立虛擬地址與物理地址的映射關系。之后,程序就可以通過新建立的映射關系訪問到數據了。
為了更直觀地理解,我們可以把虛擬內存想象成一個圖書館的目錄系統。每個進程就像是一個讀者,擁有自己的目錄(虛擬地址空間)。當讀者想要查找某本書(訪問數據)時,會先在自己的目錄中找到對應的條目(虛擬地址),然后通過這個條目去書架(物理內存)上找到實際的書。如果書架上沒有這本書(缺頁異常),圖書館管理員(操作系統)就會從倉庫(磁盤)中把書取出來放到書架上,并更新目錄(頁表),以便下次讀者能更快地找到這本書。
例如:對于程序計數器位數為32位的處理器來說,他的地址發生器所能發出的地址數目為2^32=4G個,于是這個處理器所能訪問的最大內存空間就是4G。在計算機技術中,這個值就叫做處理器的尋址空間或尋址能力。
照理說,為了充分利用處理器的尋址空間,就應按照處理器的最大尋址來為其分配系統的內存。如果處理器具有32位程序計數器,那么就應該按照下圖的方式,為其配備4G的內存:
圖片
這樣,處理器所發出的每一個地址都會有一個真實的物理存儲單元與之對應;同時,每一個物理存儲單元都有唯一的地址與之對應。這顯然是一種最理想的情況。
但遺憾的是,實際上計算機所配置內存的實際空間常常小于處理器的尋址范圍,這是就會因處理器的一部分尋址空間沒有對應的物理存儲單元,從而導致處理器尋址能力的浪費。例如:如下圖的系統中,具有32位尋址能力的處理器只配置了256M的內存儲器,這就會造成大量的浪費:
圖片
另外,還有一些處理器因外部地址線的根數小于處理器程序計數器的位數,而使地址總線的根數不滿足處理器的尋址范圍,從而處理器的其余尋址能力也就被浪費了。例如:Intel8086處理器的程序計數器位32位,而處理器芯片的外部地址總線只有20根,所以它所能配置的最大內存為1MB:
圖片
在實際的應用中,如果需要運行的應用程序比較小,所需內存容量小于計算機實際所配置的內存空間,自然不會出什么問題。但是,目前很多的應用程序都比較大,計算機實際所配置的內存空間無法滿足。
實踐和研究都證明:一個應用程序總是逐段被運行的,而且在一段時間內會穩定運行在某一段程序里。
這也就出現了一個方法:如下圖所示,把要運行的那一段程序自輔存復制到內存中來運行,而其他暫時不運行的程序段就讓它仍然留在輔存。
圖片
當需要執行另一端尚未在內存的程序段(如程序段2),如下圖所示,就可以把內存中程序段1的副本復制回輔存,在內存騰出必要的空間后,再把輔存中的程序段2復制到內存空間來執行即可:
圖片
在計算機技術中,把內存中的程序段復制回輔存的做法叫做“換出”,而把輔存中程序段映射到內存的做法叫做“換入”。經過不斷有目的的換入和換出,處理器就可以運行一個大于實際物理內存的應用程序了。或者說,處理器似乎是擁有了一個大于實際物理內存的內存空間。于是,這個存儲空間叫做虛擬內存空間,而把真正的內存叫做實際物理內存,或簡稱為物理內存。
那么對于一臺真實的計算機來說,它的虛擬內存空間又有多大呢?計算機虛擬內存空間的大小是由程序計數器的尋址能力來決定的。例如:在程序計數器的位數為32的處理器中,它的虛擬內存空間就為4GB。
可見,如果一個系統采用了虛擬內存技術,那么它就存在著兩個內存空間:虛擬內存空間和物理內存空間。虛擬內存空間中的地址叫做“虛擬地址”;而實際物理內存空間中的地址叫做“實際物理地址”或“物理地址”。處理器運算器和應用程序設計人員看到的只是虛擬內存空間和虛擬地址,而處理器片外的地址總線看到的只是物理地址空間和物理地址。
由于存在兩個內存地址,因此一個應用程序從編寫到被執行,需要進行兩次映射。第一次是映射到虛擬內存空間,第二次時映射到物理內存空間。在計算機系統中,第兩次映射的工作是由硬件和軟件共同來完成的。承擔這個任務的硬件部分叫做存儲管理單元MMU,軟件部分就是操作系統的內存管理模塊了。
在映射工作中,為了記錄程序段占用物理內存的情況,操作系統的內存管理模塊需要建立一個表格,該表格以虛擬地址為索引,記錄了程序段所占用的物理內存的物理地址。這個虛擬地址/物理地址記錄表便是存儲管理單元MMU把虛擬地址轉化為實際物理地址的依據,記錄表與存儲管理單元MMU的作用如下圖所示:
圖片
綜上所述,虛擬內存技術的實現,是建立在應用程序可以分成段,并且具有“在任何時候正在使用的信息總是所有存儲信息的一小部分”的局部特性基礎上的。它是通過用輔存空間模擬RAM來實現的一種使機器的作業地址空間大于實際內存的技術。
從處理器運算裝置和程序設計人員的角度來看,它面對的是一個用MMU、映射記錄表和物理內存封裝起來的一個虛擬內存空間,這個存儲空間的大小取決于處理器程序計數器的尋址空間。
可見,程序映射表是實現虛擬內存的技術關鍵,它可給系統帶來如下特點:
- 系統中每一個程序各自都有一個大小與處理器尋址空間相等的虛擬內存空間;
- 在一個具體時刻,處理器只能使用其中一個程序的映射記錄表,因此它只看到多個程序虛存空間中的一個,這樣就保證了各個程序的虛存空間時互不相擾、各自獨立的;
- 使用程序映射表可方便地實現物理內存的共享。
二、Linux 虛擬內存工作機制
2.1內存映射機制
以存儲單元為單位來管理顯然不現實,因此Linux把虛存空間分成若干個大小相等的存儲分區,Linux把這樣的分區叫做頁。為了換入、換出的方便,物理內存也就按也得大小分成若干個塊。由于物理內存中的塊空間是用來容納虛存頁的容器,所以物理內存中的塊叫做頁框。頁與頁框是Linux實現虛擬內存技術的基礎。
虛擬內存的頁、物理內存的頁框及頁表
在Linux中,頁與頁框的大小一般為4KB。當然,根據系統和應用的不同,頁與頁框的大小也可有所變化。
物理內存和虛擬內存被分成了頁框與頁之后,其存儲單元原來的地址都被自然地分成了兩段,并且這兩段各自代表著不同的意義:高位段分別叫做頁框碼和頁碼,它們是識別頁框和頁的編碼;低位段分別叫做頁框偏移量和頁內偏移量,它們是存儲單元在頁框和頁內的地址編碼。下圖就是兩段虛擬內存和物理內存分頁之后的情況:
圖片
為了使系統可以正確的訪問虛存頁在對應頁框中的映像,在把一個頁映射到某個頁框上的同時,就必須把頁碼和存放該頁映像的頁框碼填入一個叫做頁表的表項中。這個頁表就是之前提到的映射記錄表。一個頁表的示意圖如下所示:
圖片
頁模式下,虛擬地址、物理地址轉換關系的示意圖如下所示:
圖片
也就是說:處理器遇到的地址都是虛擬地址。虛擬地址和物理地址都分成頁碼(頁框碼)和偏移值兩部分。在由虛擬地址轉化成物理地址的過程中,偏移值不變。而頁碼和頁框碼之間的映射就在一個映射記錄表——頁表中。
話說回來,內存映射是 Linux 中一種重要的內存管理技術,它允許將一個文件或者其他對象映射到進程的虛擬地址空間中,使得進程可以像訪問內存一樣直接訪問文件 。這種技術的核心優勢在于提高了文件訪問的效率,減少了內核和用戶空間之間的數據拷貝。在 Linux 中,內存映射主要通過mmap()系統調用實現。
mmap()函數將文件或其他對象映射到虛擬地址空間的一個連續區域,返回一個指向映射區域開始地址的指針 。對該指針進行讀寫操作,實際上就是在訪問文件內容。使用munmap()函數可以解除內存映射。例如,在 C 語言中,可以這樣使用mmap()函數:
#include <sys/mman.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
int fd;
char *map_start;
off_t file_size;
// 打開文件
fd = open("test.txt", O_RDWR);
if (fd == -1) {
perror("open");
return 1;
}
// 獲取文件大小
file_size = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
// 創建內存映射
map_start = (char *)mmap(0, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map_start == MAP_FAILED) {
perror("mmap");
close(fd);
return 1;
}
// 關閉文件描述符,映射依然有效
close(fd);
// 訪問映射內存,就像訪問文件一樣
printf("Content of file: %s\n", map_start);
// 修改映射內存中的內容
sprintf(map_start, "This is a new content");
// 解除內存映射
if (munmap(map_start, file_size) == -1) {
perror("munmap");
return 1;
}
return 0;
}
在這個例子中,首先打開一個文件,然后使用mmap()函數將文件映射到進程的虛擬地址空間。通過返回的指針map_start,可以像訪問普通內存一樣訪問文件內容。修改map_start指向的內存區域,實際上就是在修改文件內容。最后,使用munmap()函數解除內存映射。
內存映射在實際應用中有很多場景。比如在共享庫的加載中,多個進程可以映射同一個共享庫文件,實現代碼和數據的共享,減少內存占用 。在文件 I/O 操作中,對于大文件的讀寫,內存映射可以避免頻繁的系統調用和數據拷貝,提高讀寫效率。例如,數據庫系統通常會使用內存映射來處理數據文件,加快數據的讀取和寫入速度。
2.2請頁與交換
虛存頁面到物理頁框的映射叫做頁面的加載
當處理器試圖訪問一個虛存頁面時,首先到頁表中去查詢該頁是否已映射到物理頁框中,并記錄在頁表中。如果在,則MMU會把頁碼轉換成頁框碼,并加上虛擬地址提供的頁內偏移量形成物理地址后去訪問物理內存;如果不在,則意味著該虛存頁面還沒有被載入內存,這時MMU就會通知操作系統:發生了一個頁面訪問錯誤(頁面錯誤),接下來系統會啟動所謂的“請頁”機制,即調用相應的系統操作函數,判斷該虛擬地址是否為有效地址。
如果是有效的地址,就從虛擬內存中將該地址指向的頁面讀入到內存中的一個空閑頁框中,并在頁表中添加上相對應的表項,最后處理器將從發生頁面錯誤的地方重新開始運行;如果是無效的地址,則表明進程在試圖訪問一個不存在的虛擬地址,此時操作系統將終止此次訪問。
當然,也存在這樣的情況:在請頁成功之后,內存中已沒有空閑物理頁框了。這是,系統必須啟動所謂地“交換”機制,即調用相應的內核操作函數,在物理頁框中尋找一個當前不再使用或者近期可能不會用到的頁面所占據的頁框。找到后,就把其中的頁移出,以裝載新的頁面。對移出頁面根據兩種情況來處理:如果該頁未被修改過,則刪除它;如果該頁曾經被修改過,則系統必須將該頁寫回輔存。
系統請頁的處理過程如下所示:
為了公平地選擇將要從系統中拋棄的頁面,Linux系統使用最近最少使用(LRU)頁面的衰老算法。這種策略根據系統中每個頁面被訪問的頻率,為物理頁框中的頁面設置了一個叫做年齡的屬性。頁面被訪問的次數越多,則頁面的年齡最小;相反,則越大。而年齡較大的頁面就是待換出頁面的最佳候選者。
圖片
2.3快表
在系統每次訪問虛存頁時,都要在內存的所有頁表中尋找該頁的頁框,這是一個很費時間的工作。但是,人們發現,系統一旦訪問了某一個頁,那么系統就會在一段時間內穩定地工作在這個頁上。所以,為了提高訪問頁表的速度,系統還配備了一組正好能容納一個頁表的硬件寄存器,這樣當系統再訪問虛存時,就首先到這組硬件寄存器中去訪問,系統速度就快多了。這組存放當前頁表的寄存器叫做快表。
總之,使用虛擬存儲技術時,處理器必須配備一些硬件來承擔內存管理的一部分任務。承擔內存管理任務的硬件部分叫做存儲管理單元MMU。存儲管理單元MMU的工作過程如下圖所示:
圖片
(1)頁的共享
在多程序系統中,常常有多個程序需要共享同一段代碼或數據的情況。在分頁管理的存儲器中,這個事情很好辦:讓多個程序共享同一個頁面即可。
具體的方法是:使這些相關程序的虛擬空間的頁面在頁表中指向內存中的同一個頁框。這樣,當程序運行并訪問這些相關頁面時,就都是對同一個頁框中的頁面進行訪問,而該頁框中的頁就被這些程序所共享。下圖是3個程序共享一個頁面的例子:
圖片
(2)頁的保護
由上可知,頁表實際上是由虛擬空間轉到物理空間的入口。因此,為了保護頁面內容不被沒有該頁面訪問權限的程序所破壞,就應在頁表的表項中設置一些訪問控制字段,用于指明對應頁面中的內容允許何種操作,從而禁止非法訪問。下圖是頁表項中存放控制信息的一種可能的形式:
圖片
注意:其中的PCD位表示著是否允許高速緩存(cache)。
如果程序對一個頁試圖進行一個該頁控制字段所不允許的操作,則會引起操作系統的一次中斷——非法訪問中斷,并拒絕這種操作,從而保護該頁的內容不被破壞。
(3)多級頁表
需要注意的是,頁表是操作系統創建的用于內存管理的表格。因此,一個程序在運行時,其頁表也要存放到內存空間。如果一個程序只需要一個頁表,則不會有什么問題。但如果,程序的虛擬空間很大的話,就會出現一個比較大的問題。
比如:一個程序的虛擬空間為4GB,頁表以4KB為一頁,那么這個程序空間就是1M頁。為了存儲這1M頁的頁指針,那么這個頁表的長度就相當大了,對內存的負擔也很大了。所以,最好對頁表也進行分頁存儲,在程序運行時只把需要的頁復制到內存,而暫時不需要的頁就讓它留在輔存中。為了管理這些頁表頁,還要建立一個記錄頁表頁首地址的頁目錄表,于是單級頁表就變成了二級頁表。二級頁表的地址轉換如下圖所示:
圖片
當然,如果程序的虛擬空間更大,那么也可以用三級頁表來管理。為了具有通用性,Linux系統使用了三級頁表結構:頁目錄(Page Directory,PGD)、中間頁目錄(Page Middle Directory,PMD)、頁表(Page Table,PTE)。
2.4Linux的頁表結構
為了通用,Linux系統使用了三級頁表結構:頁目錄、中間頁目錄和頁表。PGD為頂級頁表,是一個pgd_t數據類型(定義在文件linux/include/page.h中)的數組,每個數組元素指向一個中間頁目錄;PMD為二級頁表,是一個pmd_t數據結構的數組,每個數組元素指向一個頁表;PTE則是頁表,是一個pte_t數據類型的數組,每個元素中含有物理地址。
圖片
為了應用上的靈活,Linux使用一系列的宏來掩蓋各種平臺的細節。用戶可以在配置文件config中根據自己的需要對頁表進行配置,以決定是使用三級頁表還是使用二級頁表。
在系統編譯時,會根據配置文件config中的配置,把目錄include/asm符號連接到具體CPU專用的文件目錄中。例如,對于i386CPU,該目錄符號會連接到include/asm-i386,并在文件pgable-2level-defs.h中定義了二級頁表的基本結構,如下圖:
圖片
其中還定義了:
#define PGDIR_SHIFT 22 //PGD在線性地址中的起始地址為bit22
#define PTRS_PER_PGD 1024 //PGD共有1024個表項
#define PTRS_PER_PTE 1024 //PTE共有1024個表項
#endif
在文件include/asm-i386/pgtable.h中定義了頁目錄和頁表項的數據結構,如下:
typedof struct { unsigned long pte_low; } pte_t; //頁表中的物理地址,頁框碼
typedof struct { unsigned long pgd; } pgd_t; //指向一個頁表
typedof struct { unsigned long pgprot; } pgprot_t; //頁表中的各個狀態信息和訪問權限
從定義可知,它們都是只有一個長整型類型(32位)的結構體。
注意:如上文的“頁的保護”部分,頁框碼代表物理地址,只需要高20位就夠了(因為頁框的長度為4KB,因此頁內偏移12位)。而后12位可以存放各個狀態信息和訪問權限。但是Linux并沒有這樣做,反而重新定義了一個結構體來存放,通過“或”運算來將兩者結合。
2.5Swap 交換機制
首先呢,提一個概念,交換空間(swap space),這個大家應該不陌生,在重裝系統的時候,會讓你選擇磁盤分區,就比如說一個硬盤分幾個部分去管理。其中就會分一部分磁盤空間用作交換,叫做swap space。其實就是一段臨時存儲空間,內存不夠用的時候就用它了,雖然它也在磁盤中,但省去了很多的查找時間啊。當發生進程切換的時候,內存與交換空間就要發生數據交換一滿足需求。所以啊,進程的切換消耗是很大的,這也說明了為什么自旋鎖比信號量效率高的原因。
那么我們的程序里申請的內存的時候,linux內核其實只分配一個虛擬內存( 線性地址),并沒有分配實際的物理內存。只有當程序真正使用這塊內存時,才會分配物理內存。這就叫做延遲分配和請頁機制。釋放內存時,先釋放線性區對應的物理內存,然后釋放線性區;"請頁機制"將物理內存的分配延后了,這樣是充分利用了程序的局部性原來,節約內存空間,提高系統吞吐;就是說一個函數可能只在物理內存中呆了一會,用完了就被清除出去了,雖然在虛擬地址空間還在。(不過虛擬地址空間不是事實上的存儲,所以只能說這個函數占據了一段虛擬地址空間,當你訪問這段地址時,就會產生缺頁處理,從交換區把對應的代碼搬到物理內存上來)
Swap 交換機制是 Linux 虛擬內存管理的另一個重要組成部分。簡單來說,Swap 是磁盤上的一塊區域,當物理內存不足時,系統會將一部分暫時不用的內存頁面(Page)交換到 Swap 空間中,騰出物理內存給更需要的進程使用 。當被交換出去的頁面再次被訪問時,系統會將其從 Swap 空間換回到物理內存中。
Swap 交換機制的工作原理涉及到內存回收和頁面置換算法。當系統內存緊張時,內核會啟動內存回收機制,掃描內存中的頁面,選擇一些不常用或最近最少使用的頁面進行回收。如果這些頁面是匿名頁面(沒有關聯到文件的內存頁面,如進程的堆和棧空間),就會被交換到 Swap 空間中;如果是文件映射頁面(關聯到文件的內存頁面,如共享庫、文件緩存等),則會根據情況進行處理,臟頁面(被修改過的頁面)會被寫回文件,干凈頁面(未被修改過的頁面)可以直接釋放。
Swap 交換機制對系統性能有著重要的影響。當 Swap 使用頻繁時,說明物理內存不足,系統需要頻繁地在物理內存和 Swap 空間之間交換頁面,這會導致磁盤 I/O 增加,系統性能下降 。因為磁盤的讀寫速度遠遠低于內存,過多的 Swap 操作會使系統變得遲緩。因此,在實際應用中,需要合理配置 Swap 空間的大小,并密切關注系統的內存使用情況,避免 Swap 過度使用。
例如,可以通過調整/proc/sys/vm/swappiness參數來控制系統對 Swap 的使用傾向,swappiness的值范圍是 0 - 100,表示系統將內存頁面交換到 Swap 空間的傾向程度,值越大表示越傾向于使用 Swap 。一般來說,對于內存充足的系統,可以將swappiness設置為較低的值,如 10 或 20,以減少不必要的 Swap 操作;對于內存緊張的系統,可以適當提高swappiness的值,但也要注意不要過高,以免嚴重影響性能。
三、Linux 虛擬內存管理工具
在 Linux 系統中,有多個實用工具可以幫助我們查看和管理虛擬內存,了解系統的內存使用狀態,下面介紹幾個常用的命令。
3.1top 命令
top命令是一個功能強大的系統監控工具,它能夠實時顯示系統中各個進程的資源使用情況,包括CPU、內存等 。通過 top 命令,我們可以直觀地了解到系統中哪些進程占用了較多的虛擬內存。在終端中輸入 “top”,即可啟動該命令,其輸出結果大致如下:
top - 14:20:12 up 2 days, 1:23, 2 users, load average: 0.00, 0.01, 0.05
Tasks: 152 total, 1 running, 151 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.3 us, 0.3 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 8161844 total, 7433924 free, 149744 used, 578176 buff/cache
KiB Swap: 2097148 total, 2097148 free, 0 used. 7663360 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 12844 7448 4564 S 0.0 0.1 0:02.33 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
在 top 命令的輸出中,與虛擬內存相關的主要是 “VIRT” 列,它表示進程使用的虛擬內存總量,單位為 KB 。例如,上述輸出中 PID 為 1 的 systemd 進程,其 VIRT 值為 12844KB,表示該進程使用了 12844KB 的虛擬內存。此外,“RES” 列表示進程使用的物理內存大小,“SHR” 列表示共享內存大小 。通過觀察這些指標,我們可以了解每個進程對內存資源的占用情況,判斷是否存在內存使用異常的進程。在實際應用中,如果發現某個進程的 VIRT 值持續增長且占用大量虛擬內存,可能需要進一步分析該進程的行為,看是否存在內存泄漏等問題。比如,在一個長時間運行的服務器程序中,如果其 VIRT 值不斷上升,而業務量并沒有明顯增加,就需要檢查程序代碼,查看是否有未釋放的內存資源。
3.2free 命令
free 命令用于顯示系統內存的使用情況,包括物理內存和虛擬內存(交換空間) 。它可以幫助我們快速了解系統內存的整體使用狀態,判斷是否存在內存不足的情況。在終端中輸入 “free”,輸出結果如下:
total used free shared buff/cache available
Mem: 8161844 149744 7433924 34624 578176 7663360
Swap: 2097148 0 2097148
在 free 命令的輸出中,“total” 表示系統內存的總量,“used” 表示已使用的內存量,“free” 表示空閑內存量,“shared” 表示共享內存量,“buff/cache” 表示緩沖區和緩存使用的內存量,“available” 表示應用程序還可以申請到的內存 。其中,與虛擬內存相關的是 “Swap” 部分,“Swap total” 表示交換空間的總量,“Swap used” 表示已使用的交換空間量,“Swap free” 表示空閑的交換空間量 。例如,上述輸出中 Swap total 為 2097148KB,Swap used 為 0KB,Swap free 為 2097148KB,說明當前系統的交換空間未被使用,這通常是一個比較理想的狀態,意味著系統的物理內存充足,不需要頻繁地進行內存交換操作。如果 Swap used 的值較大,說明系統的物理內存可能不足,需要將一部分內存數據交換到磁盤的交換空間中,這可能會導致系統性能下降,因為磁盤的讀寫速度遠遠低于內存。在這種情況下,我們可以考慮增加物理內存,或者優化系統的內存使用,減少不必要的內存占用。
3.3vmstat 命令
vmstat(Virtual Memory Statistics)命令用于顯示虛擬內存統計信息,同時也可以展示進程、CPU、I/O 等系統整體運行狀態 。它提供了更詳細的內存使用信息,對于深入分析系統性能非常有幫助。在終端中輸入 “vmstat”,輸出結果如下:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 7433924 149744 578176 0 0 1 1 1 1 0 0 100 0 0
在 vmstat 命令的輸出中,與虛擬內存相關的字段有 “swpd”“si” 和 “so” 。“swpd” 表示使用的虛擬內存量(單位為 KB),“si” 表示從磁盤交換到內存的內存量(單位為 KB/s),“so” 表示從內存交換到磁盤的內存量(單位為 KB/s) 。例如,上述輸出中 swpd 為 0,表示當前系統沒有使用虛擬內存;si 和 so 都為 0,表示當前沒有發生內存交換操作。
當 si 和 so 的值不為 0 時,說明系統正在進行內存交換,數值越大,說明內存交換越頻繁,這可能會對系統性能產生較大影響,需要進一步分析原因,采取相應的優化措施,如增加物理內存、調整應用程序的內存使用策略等。此外,vmstat 命令還可以通過指定刷新時間間隔和刷新次數來持續監控系統狀態,例如 “vmstat 2 10” 表示每 2 秒刷新一次,共刷新 10 次,這樣可以更直觀地觀察系統內存使用情況的變化趨勢。
四、Linux 虛擬內存優化策略
4.1調整內核參數
在 Linux 系統中,通過調整內核參數可以對虛擬內存的性能進行優化 。其中,vm.swappiness是一個非常重要的內核參數,它的值表示系統將內存頁面交換到 Swap 空間的傾向程度,取值范圍是 0 - 100 。當vm.swappiness的值為 0 時,系統盡量不使用 Swap 空間,只有在物理內存完全耗盡時才會考慮交換;當值為 100 時,系統會非常積極地將內存頁面交換到 Swap 空間 。
可以使用sysctl命令來查看和臨時修改vm.swappiness的值。例如,要查看當前vm.swappiness的值,可以在終端中輸入:
sysctl vm.swappiness
如果要將vm.swappiness的值臨時修改為 10,可以使用以下命令:
sysctl vm.swappiness=10
這種修改在系統重啟后會失效。如果想要永久修改vm.swappiness的值,可以編輯/etc/sysctl.conf文件,添加或修改vm.swappiness = 10這一行,然后執行sysctl -p使修改生效 。
vm.vfs_cache_pressure也是一個重要的內核參數,它控制著文件系統緩存(VFS Cache)被回收的傾向 。該參數的值越大,文件系統緩存就越容易被回收;值越小,文件系統緩存就越不容易被回收 。默認值通常為 100。如果系統中文件 I/O 操作頻繁,可以適當降低vm.vfs_cache_pressure的值,以減少文件系統緩存的回收,提高文件訪問性能 。同樣,可以使用sysctl命令來查看和修改這個參數,例如:
sysctl vm.vfs_cache_pressure
sysctl vm.vfs_cache_pressure=50
修改內核參數對虛擬內存性能有著顯著的影響。合理調整vm.swappiness可以避免系統過度依賴 Swap 空間,減少磁盤 I/O 操作,提高系統整體性能 。如果vm.swappiness設置過高,系統頻繁進行內存交換,會導致磁盤 I/O 負載增加,系統響應變慢;如果設置過低,當物理內存不足時,可能會導致進程因無法獲取足夠內存而被終止 。而調整vm.vfs_cache_pressure則可以優化文件系統緩存的使用,提高文件讀寫效率,對于那些依賴文件系統的應用程序(如數據庫、文件服務器等)來說,合理設置該參數能有效提升其性能 。
4.2優化應用程序
應用程序的內存使用方式對虛擬內存性能有著直接的影響 。在實際開發中,常常會出現應用程序內存使用不當的情況,從而導致虛擬內存的浪費和系統性能的下降 。比如,在一些 Web 應用程序中,可能會存在內存泄漏的問題。以使用 Python 的 Flask 框架開發的 Web 應用為例,如果在視圖函數中創建了大量的對象,卻沒有及時釋放,隨著時間的推移,這些未釋放的對象會占用越來越多的內存,導致虛擬內存不斷增加 。假設我們有如下代碼:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
data = []
for i in range(10000):
# 創建大量對象但未及時釋放
obj = {'key': i}
data.append(obj)
return 'Hello, World!'
if __name__ == '__main__':
app.run()
在這個簡單的示例中,每次訪問/路由時,都會創建 10000 個字典對象并添加到data列表中,但在函數結束后,這些對象并沒有被及時釋放,會造成內存浪費。為了優化應用程序的內存使用,可以采用以下方法:
首先,使用內存分析工具(如 Python 中的memory_profiler、C++ 中的Valgrind等)來檢測應用程序中的內存泄漏和不合理的內存使用 。通過這些工具,可以定位到具體的代碼行,找出內存問題的根源 。其次,合理設計數據結構和算法,減少不必要的內存占用 。比如,在存儲大量整數數據時,如果數據范圍較小,可以使用uint8_t(無符號 8 位整數)類型代替int(通常為 32 位或 64 位整數)類型,這樣可以節省內存空間 。
另外,對于一些頻繁創建和銷毀的對象,可以考慮使用對象池技術 。以數據庫連接池為例,在一個 Web 應用中,如果每次處理請求都創建新的數據庫連接,會消耗大量資源。使用數據庫連接池后,可以預先創建一定數量的數據庫連接對象,當有請求時,直接從連接池中獲取連接,使用完畢后再放回連接池,避免了頻繁創建和銷毀連接對象帶來的開銷 。
優化應用程序的內存使用對虛擬內存性能有著重要的作用。通過減少內存泄漏和優化內存使用,可以降低應用程序對虛擬內存的需求,減少系統內存管理的負擔,提高系統的整體性能和穩定性 。當應用程序能夠高效地使用內存時系統可以將更多的內存資源分配給其他需要的進程,避免因內存不足而導致的頻繁內存交換和系統性能下降 。
4.3合理配置 Swap 空間
Swap空間的大小和配置對系統性能有著顯著的影響 。如果Swap空間設置得太小,當物理內存不足時,系統可能無法及時將內存頁面交換到 Swap 空間中,導致進程因無法獲取足夠內存而出現異常,甚至系統崩潰 。相反,如果Swap空間設置得過大,會占用過多的磁盤空間,而且在系統不需要使用Swap空間時,這些空間就被浪費了 。
在 Linux 系統中,可以通過創建交換分區或交換文件的方式來設置 Swap 空間 。以創建交換文件為例,首先需要使用dd命令創建一個指定大小的文件,例如創建一個大小為 2GB 的交換文件:
sudo dd if=/dev/zero of=/swapfile bs=1M count=2048
上述命令中,if=/dev/zero表示從/dev/zero設備讀取數據(/dev/zero是一個特殊的設備文件,它會不斷返回 0 值字節流),of=/swapfile表示將數據寫入到/swapfile文件中,bs=1M表示每次讀寫的數據塊大小為 1MB,count=2048表示總共讀寫 2048 次,即創建一個 2GB 大小的文件 。
創建好文件后,需要使用mkswap命令將其格式化為交換文件:
sudo mkswap /swapfile
最后,使用swapon命令啟用這個交換文件:
sudo swapon /swapfile
如果希望系統在每次啟動時自動啟用這個交換文件,可以將其添加到/etc/fstab文件中,在文件末尾添加一行:
/swapfile none swap sw 0 0
除了設置 Swap 空間的大小,還可以設置 Swap 空間的優先級 。在 Linux 系統中,每個 Swap 設備都有一個優先級,取值范圍是 - 2^31 到 2^31 - 1 。優先級較高的 Swap 設備會優先被使用 。可以使用swapon命令的-p選項來設置 Swap 設備的優先級,例如:
sudo swapon -p 10 /swapfile
上述命令將/swapfile這個交換文件的優先級設置為 10 。如果系統中有多個 Swap 設備,可以根據實際需求為它們設置不同的優先級,以優化內存交換的性能 。比如,對于讀寫速度較快的 SSD 磁盤上的 Swap 分區,可以設置較高的優先級,使其在內存交換時優先被使用,減少因磁盤 I/O 速度慢而導致的性能下降 。