Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
【作者】陳熾卉,15年Power服務器領域工作經驗,高級技術支持中心性能調優專家,紅皮書《Power Systems Guide: and 》主要作者之一。長期在金融、電信行業從事技術支持工作;對應用開發、系統優化、故障定位、基準測試等方面均有豐富經驗。
目錄
1. AIX 內存分配回收策略
1.1 內存分配觀察示例—遞增分配
1.2 內存分配觀察示例—遞減分配
1.3 針對長運行程序的空閑內存回收
1.4 mallopt 示例 1
1.5 mallopt 示例 2
1.6 內存回收 策略
1.7 / 代碼示例
1.8 內存碎片對內存回收的影響
1.9 通用建議
2. 內存監控
2.1 觀察系統中內存占用最高的進程
2.2 尋找內存持續增長的進程
2.3 如何通過共享內存 ID 對應關聯到該共享內存的進程
2.4 如何獲取 AIX Kernel 的內存使用率
2.5 如何判斷系統是否存在內存不足
3. 應用開發工具
3.1 dbx 使用以及 定位
3.2 內存非法使用檢查
3.3 內存泄漏檢查()
3.4 內存泄漏檢查示例
3.5 介紹
3.6 介紹
3.7 如何將 C 文件與匯編文件對應
1.AIX 內存分配回收策略
一般而言, 系統會直接在進程空間的free列表中維護其free釋放的內存, 以供后續新的分配直接使用,這樣可以提高分配效率,不需要每次內存分配都經過系統內核。進程退出后,系統會回收該進程占用的全部內存。
注:選擇不同的分配策略時,對空閑內存空間的管理策略會有所差異。例如默認的管理結構是樹;而采用watson分配算法時,使用的管理結構是紅黑樹。
笛卡爾樹參考結構:
1.1 內存分配觀察示例—遞增分配
進程的詳細內存分配情況可以使用svmon來觀察,參考如下示例。
需要注意,為方便svmon觀察,示例代碼需要在malloc之后調用memset進行初始化;因為操作系統實際上并不會立即對已申請但尚未訪問到的內容分配實際存儲空間, 而是推遲到第一次訪問時才會實際分配---這即是缺頁機制的工作原理。
如下是一個申請空間遞增的應用,分配/釋放大小為2MB->4MB->8MB->16MB, 則通過各階段的svmon可以看到, 內存頁面會持續增長, 從2MB一直增加到16MB (注意 不是2MB+4MB+..+16MB=30MB) 。
Malloc分配2MB,未初始化時:
Address Range為0~512頁,即代表512×4096=2MB虛擬地址空間。Virtual取值為2,表示該空間尚未實際分配。
初始化后:
Virtual取值為513,表明虛存空間已經實際分配。
釋放之前申請的2MB,重新 申請 4MB 并初始化后:
1024×4096=4MB,此前釋放的512頁虛擬地址空間被重復利用。
釋放之前申請的4MB,重新 申請8 8 MB 并初始化后:
此前釋放的1024頁虛擬地址空間被重復利用。
釋放之前申請的8MB,重新 申請 16 MB 并初始化后:
1.2 內存分配觀察示例—遞減分配
如示例, 如果是一個申請空間遞減的應用, 分配/釋放大小為16MB->8MB->4MB->2MB, 通過各階段的svmon(svmon -nrP
)可以看到,內存頁面始終維持在16MB。
Malloc分配16MB,未初始化時:
Address Range為0~4096頁,即代表4096×4096=16MB虛擬地址空間。Inuse/Virtual取值為2,表示該空間尚未實際分配。
初始化后:
Virtual=4097頁, 虛擬內存已經實際分配。
釋放之前申請的16MB,重新 申請8 8 MB 并初始化后:
釋放之前申請的8MB,重新 申請4 4 MB 并初始化后:
釋放之前申請的4MB,重新 申請2 2 MB 并初始化后:
可以看到svmon輸出結果沒有變化;原因是雖然應用調用了free釋放了16MB內存,但系統的處理策略是將該內存置于進程自身的空間塊樹中管理。下一個8MB分配,實際上是直接從進程已有的16MB空閑塊中獲取的。但對系統而言,進程管理的空閑塊樹也對應為該進程的內存消耗,所以其內存占用沒有變化。
測試代碼:
1.3 針對長運行程序的空閑內存回收
由于上面介紹的內存使用方式,一個常見的現象是,對于長運行進程,其占用的私有內存大小等同于最高峰時間的內存大小。
這個現象一般而言,對系統正常運行及性能的影響很小。因為如果系統空閑內存足夠,這個問題不存在。而如果內存不足,由于進程本身的空閑塊列表沒有被引用,根據換頁算法,在系統缺頁(即可用內存不足)時,很容易被換出。
但如果應用進程希望對其空間內存塊進行更自主的管理,可以使用mallopt接口。例如,對長運行的程序,定期在其閑時(例如每周日凌晨業務量極低時),調用 mallopt(, 0)釋放進程私有空間的free列表。這樣可以避免在程序本身沒有內存泄漏的情況下,因業務高峰期大量的內存申請造成的進程私有free list增長,而使得進程內存占用過大。實現起來也相當簡單(參考如下 mallopt 示例)。
參考mallopt函數的幫助信息:
1.4 mallopt 示例 1
1.5 mallopt 示例 2
1.6 內存回收 策略
如果需要設置使得系統在應用調用 free 之后,即回收其內存,可以在程序啟動前設置環境變量(這兩種方式都可能對程序性能有很大影響):
PSALLOC=early或者=
由于PSALLOC=early實際上意味著設置=,且提前分配Paging Space;所以其性能開銷還要高于=。但這種機制能夠避免Paging Space耗盡時,進程被系統kill掉的可能;詳情可以通過在如下aix 網站查詢PSALLOC獲取。
注:
老的=3.1也有此效果,但該分配策略往往性能一般,且只適用于32位程序,現在的系統一般不使用了。
在設置環境變量=watson的情況下,mallopt(,0)無效;
mallopt(,0)只對系統默認的分配策略(即樹分配算法)有效。
也可以在代碼中直接用調用。注意 調用需要在 free /delete 之前;超過 4G 的內存塊需要使用 函數。
1.7 / 代碼示例
1.8 內存碎片對內存回收的影響
需要注意的是,上面各種釋放進程私有空閑內存列表的方法,在應用出現嚴重內存碎片的情況下,都存在不足。這種情況需要實際應用程序通過合理設計,避免嚴重內存碎片。
例如一些緩存數據庫表的數據結構,可能涉及大量記錄的增刪,且一般不會一次性全部刪除,類似這種情況可能最終造成嚴重內存碎片;從而使得進程私有空間無法有效回收, 只能存在于進程自身的私有free list中。
舉一個極端的例子,一個進程申請了4000頁內存,但釋放時,恰好在每個內存頁上保留了一個16字節的數據結構(其他空間均釋放)。這樣進程的私有內存空間實際上無法進行收縮。因為mallopt(, 0)和等方法都需要以多個頁面為單位進行實際回收。
但如果應用設計良好,一般可以避免這類問題:例如一些事務性的應用,在事務處理開始時,大量申請內存;在處理結束后, 就將這些內存釋放。這種情況下, 出現大量內存碎片的概率較低。
1.9 通用建議
一般對內存受限程序的建議是:
a. 盡量使用malloc而不是calloc
b. 提高引用局部性,即僅在數據結構馬上要被使用前,對其進行初始化。
c. 對大的數據結構,如果使用一次后,后續不再使用,建議調用回收其分配的內存。
參考:
性能管理和調整 > 性能管理 > 性能規劃和實現 > 有效的程序設計和實現 > 內存限制程序:
要將數據工作集減至最小,請嘗試集中常用數據以及避免對虛擬存儲器頁面不必要的引用。
特別是:
i. 用 malloc() 或 calloc() 子例程來僅請求實際需要的空間大小。當實際情況只使用數組的一小部分時,切勿請求然后初始化最大的數組。當您得到一個新頁面用來初始化數組元素時,您實際上是強制 VMM 從別處竊取一個實內存頁面。隨后, 當擁有該頁面的進程嘗試再次訪問它時,會造成缺頁故障。malloc() 和 calloc() 子例程的差異不僅僅在接口上。
ii. 因為 calloc() 子例程將分配的存儲器置零, 它與每一個分配的頁面相關, 而 malloc() 子例程只與第一個頁面相關。如果您用 calloc() 子例程分配一大塊區域, 然后最初只使用一小部分,那么您對系統施加了不必要的負載。不僅這些頁面必須初始化;而且如果它們的實內存頁面被系統回收(因為系統調頁), 那么已初始化但從未使用的頁面必須寫出到調頁空間。這種情況浪費 I/O 和調頁空間。
iii. 大結構(如緩沖區)的鏈表可以引起類似的問題。如果您的程序執行大量尋找某個特定關鍵字的鏈式跟蹤,請考慮保持鏈接指針和關鍵字與數據分離或使用散列表方法來代替。
iv. 引用局部性也意味著時間上的局部性, 而不僅僅是地址空間上。僅在使用之前初始化數據結構(如果使用的話)。在重負載系統中,在初始化和使用之間長時間駐留的數據結構有幀被竊取(因為系統調頁)的危險。然后您的程序就會在開始使用數據結構時發生不必要的缺頁故障。
v. 同樣,如果早先使用一個大結構,然后與程序剩余部分無關聯,它應該被釋放。使用 free()子例程來釋放由 malloc() 或 calloc() 子例程分配的空間是不夠的。free() 子例程僅僅釋放結構占用的地址范圍。要釋放實內存和調頁空間,也可使用 () 子例程來放棄空間。對() 的調用應該在調用 free() 之前進行。
2. 內存監控
2.1 觀察系統中內存占用最高的進程
后臺運行 3 個 nmem64 進程:
./nmem64 -m 2048 -s 3000 -z 80 &
按進程使用的虛擬內存進行排序,顯示占用最高的前三項:
顯示虛擬內存占用最高的 3 個進程的詳細的內存段分布信息,如下:
可以看到,消耗最多虛擬內存的段都是 nmem64 進程的數據段。
2.2 尋找內存持續增長的進程
可以使用 ps vg 記錄當前系統中各進程的內存消耗情況,然后通過比較多次 ps vg 的結果來判斷是否存在一些進程有持續的內存增長。
說明:
進程存在持續內存增長并不一定意味著出現了內存泄漏。由于 AIX 內存分配采用了訪問時分配的策略, 進程申請大量內存時系統并不會第一時間分配內存, 而是在進程使用過程中實際訪問時才進行分配。由于這種分配策略, 進程在啟動初期可能存在內存持續增長的可能 (例如數據庫緩存需要一定時間才能完全填充) ;但其增長曲線應該是收斂到具體值的。
測試腳本如下:
2.3 如何通過共享內存 ID 對應關聯到該共享內存的進程
在 AIX 系統層面,只要給定共享內存 id,就可以獲取 attach 該共享內存的進程列表,方法如下:
可以根據其共享段 SID,獲得相應的關聯進程 pid,如下。注意 ipcs 上看 NATTACH=53,即有 53 個進程 attach 到該共享內存,因此 svmon 結果中,進程列表部分對應列出了 53 個進程 pid。
根據相應的 pid 可以獲取進程的具體信息:
2.4 如何獲取 AIX Kernel 的內存使用率
kernel大部分內存占用采用跟普通進程一樣的內存段方式組織, 比如kernel heap(這部分內存消耗包含文件系統的元數據、內核擴展、第三方驅動等等), 網絡buffer,磁盤管理LVM buffer占用, 以及一些RAS特性的buffer占用, 例如light-weight memory trace buffers, 等等。這部分都可以通過svmon -Ss 查詢出( 注意這條命令阻塞時間較長)。
少部分采用非段方式組織的主要是AIX內存管理的元數據如頁表之類。
可以通過 perfpmr.sh - - x .sh 獲取kernel內存占用的整體情況,內存分布將輸出在perfpmr.int中。
也可以通過如下方法直接觀察 AIX Kernel 內存使用情況:
1. 觀察AIX內存使用的命令(基于kdb,建議僅在測試環境驗證)
2. 建議
從我們目前實驗的幾個系統看,超過100G的大分區,一般內核的內存占用實際都在 5%以下。如果客戶環境中采用了較多第三方驅動或組件,比例可能偏高一些,建議搭建環境驗證一下。
此外, 如果系統中存在超大的JFS2文件系統, 包含大量的巨型文件 (比如單個文件數十、 數百GB) ,或者有數以十萬、百萬計的小文件;則可能觀察到較高的JFS2 元數據緩存占用,或者inode緩存占用。這部分內存消耗也會計入AIX kernel,可以通過 cat/proc/sys/fs/jfs2/觀察到:
# cat /proc/sy s/fs/jfs2/
cache:
inode cache:
total:
另,文件系統的元數據緩存和inode緩存可以通過如下ioo參數控制
這兩個參數是比例系數,設置為400時,元數據和inode緩存最多能占據系統內存的16%左右。
一般而言,如果分區內存不大或者元數據相關操作不多,使用默認值(AIX7.1為200)通常是可行的。但如果分區內存足夠大(比如100GB以上),元數據操作較多,則可以考慮將這兩個參數設置為100,可以使得元數據和inode緩存最多占用系統內存的比例下調至4%左右。
2.5 如何判斷系統是否存在內存不足
預期的內存需求是 virtual 頁面數加上文件頁面數(包括 pers 和 clnt) ,如果這兩者之和大于實際配置的內存頁面數,即可認為存在內存不足,如下示例:
AIX 6.1 TL2版本之后,加入了一個新的列:
The "" means number of bytes of memory that can be used without paging working storage.
計算大致的公式是: = free + numperm - min(numperm,minperm)- minfree
注:numperm即文件緩存所占內存;minperm為文件系統內存需要保障的比例(minperm%)換算得到的內存需求;minfree是系統內存池需要保持的空閑容量(vmo參數); 如果不夠了,就會開始 paging 了, 這是目前最直接的判斷方法。
3.4 應用開發工具
3.1 dbx 使用以及 定位
Core dump 定位參考:
使用參考“General ” “調試程序”部分。下載地址:
3.2 內存非法使用檢查
大部分的 都與內存非法使用有關。IBM 是解決這類問題的最佳工具。
使用 Purify 插樁編譯后再進行功能測試, 如果測試中發現內存非法使用之類 (包括內存泄漏)的問題, Purify 可以提供 GUI 界面直接關聯到出現問題的應用源代碼, 這樣能夠很方便的定位內存使用類的問題。
此外, 組件可以用于分析程序的性能問題。這些工具功能都非常完備。
AIX 下可免費使用的非法內存使用檢查工具是 Malloc Debug Tool,設置好 和 選項后, 啟動程序即可打開 malloc 調試功能。如下是常用的一組選項:
=debug
="log,,,,,,,output:/tmp/.txt"
如果允許越界讀(一般情況下不會造成嚴重錯誤) ,可以在 中加入。
打開 后默認的行為是發現內存使用錯誤后調用 abort 終止程序,這樣可以在第一時間 core dump 并退出,方便定位問題。但如果不希望終止可以使用 參數,這樣對于可以恢復的錯誤,將僅生成 core,進程會繼續執行。
或:
=",log:,:8,,,,,,output:/tmp/.txt"
具體可以參考:
注意:
使用 選項之后,為調試之目的,系統的內存使用可能會成倍增加,所以需要事先預留好系統的可用內存。
3.3 內存泄漏檢查()
/ 環境變量選項也可以用于內存泄露檢查,如:
=debug
=",output:/tmp/.txt"
更詳細的記錄:
=debug
=",log:,:6,output:/tmp/.txt"
原理:
選項可以用于幫助發現內存泄露問題, 選項采用數據庫記錄所有分配情況:當成功分配內存時,采用數據庫記錄分配情況;而在該內存被釋放時,則從數據庫從刪除分配記錄;
當進程退出時,系統將所有未釋放內存的詳細信息記錄到日志中。
注意事項:
1. 從上面的原理介紹可以看到, 程序必須退出之后才能確切知道那些內存沒有對應的釋放操作。因此如果是長運行的程序,必須注冊一個退出函數,常見的方法是注冊一個信號處理函數,在收到 SIGTERM 或 SIGINT 時調用 exit(0) ,參考如下示例 ( 定義和注冊過程代碼已用紅色標注)。
2. 上述 debug 環境變量應該設置為僅僅對目標測試程序生效,不能直接在用戶環境中export,否則將造成大量無關程序的分配信息被記錄到日志中,對定位問題造成干擾。
3. 激活 Malloc log 之后,對于 32 位程序,每次內存分配大約有 50-100 字節的額外開銷;對 64 位程序,每次內存分配有 100-200 字節的額外開銷。因此,激活調試選項的應用的內存使用量會有一定的增長,在測試時需要預先準備足夠的系統內存。
4. 示例如下:
參考文章:
3.4 內存泄漏檢查示例
示例 1(基本輸出) :
示例 2(擴展輸出) :
3.5 介紹
AIX 提供了一系列基于/proc 文件系統的 工具集用于分析應用問題,例如 用于打印進程當前的棧信息;procldd 顯示進程加載的共享庫信息; 顯示進程打開的文件信息;procwdx 顯示進程當前的工作目錄;procsig 顯示進程的信號處理規則(如SIGHUP,SIGTERM,SIGQUIT,SIGINT,SIGSEGV 等等) ; 顯示進程的 、real、saved user ID 和 、real、saved group ID;
參考:
#wq513
3.6 介紹
AIX6.1 開始,AIX 提供了 功能,可以用于動態跟蹤和調試。 的語法類似C 語言,參考:
此外,AIX7.1 TL2 and AIX6.1 TL8 提供了新的 特性,即關聯數組" array".可以基于這個特性實現類似 truss –c 的功能。例如如下 aso.sh 示例,可以對進程或系統范圍內的調用進行統計。
3.7 如何將 C 文件與匯編文件對應
優化編譯可能會根據語義調整代碼位置,比如將循環不變量外提,賦值傳遞等等,這樣調試的難度會增加。調試-O2 -g程序時, 打印的行信息可能并不準確。這種情況有時需要結合匯編來分析,參考下面的例子:
1. 使用 "-qsource -qlist"編譯選項, 使用該選項后,針對每個源碼文件,編譯器都會生成一個對應的".lst"文件,該文件提供了匯編和C/C++語句的對應關系。
2. 每個".lst"文件都有兩個部分,
a. 一部分是源碼與行號:
8 | j = i / 7;
b. 另一部分是相應源碼行號對應的匯編碼;
注意一些語句可能沒有直接對應的匯編定義, 比如變量定義, 或者一些已經被優化掉的邏輯之類。
參考如下示例:
參考文檔:
原題:AIX 進程內存分配與回收策略及應用開發建議
*請認真填寫需求信息,我們會在24小時內與您取得聯系。