溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Linux虛擬內(nèi)存管理方法是什么

發(fā)布時(shí)間:2021-12-23 15:54:12 來(lái)源:億速云 閱讀:117 作者:iii 欄目:云計(jì)算

本篇內(nèi)容介紹了“Linux虛擬內(nèi)存管理方法是什么”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

五.free 的內(nèi)存真的釋放了嗎(還給 OS ) ?

前面所有例子都有一個(gè)很嚴(yán)重的問(wèn)題,就是分配的內(nèi)存都沒(méi)有釋放,即導(dǎo)致內(nèi)存泄露。原則上所有 malloc/new 分配的內(nèi)存,都需 free/delete 來(lái)釋放。但是, free 了的內(nèi)存真的釋放了嗎?

要說(shuō)清楚這個(gè)問(wèn)題,可通過(guò)下面例子來(lái)說(shuō)明。

  1. 初始狀態(tài):如圖 (1) 所示,系統(tǒng)已分配 ABCD 四塊內(nèi)存,其中 ABD 在堆內(nèi)分配, C 使用 mmap 分配。為簡(jiǎn)單起見(jiàn),圖中忽略了如共享庫(kù)等文件映射區(qū)域的地址空間。

  2. E=malloc(100k) :分配 100k 內(nèi)存,小于 128k ,從堆內(nèi)分配,堆內(nèi)剩余空間不足,擴(kuò)展堆頂 (brk) 指針。

  3. free(A) :釋放 A 的內(nèi)存,在 glibc 中,僅僅是標(biāo)記為可用,形成一個(gè)內(nèi)存空洞 ( 碎片 ) ,并沒(méi)有真正釋放。如果此時(shí)需要分配 40k 以內(nèi)的空間,可重用此空間,剩余空間形成新的小碎片。

  4. free(C) : C 空間大于 128K ,使用 mmap 分配,如果釋放 C ,會(huì)調(diào)用 munmap 系統(tǒng)調(diào)用來(lái)釋放,并會(huì)真正釋放該空間,還給 OS ,如圖 (4) 所示。

  5. free(D) :與釋放 A 類似,釋放 D 同樣會(huì)導(dǎo)致一個(gè)空洞,獲得空閑空間,但并不會(huì)還給 OS 。此時(shí),空閑總空間為 100K ,但由于虛擬地址不連續(xù),無(wú)法合并,空閑空間無(wú)法滿足大于 60k 的分配請(qǐng)求。

  6. free(E) :釋放 E ,由于與 D 連續(xù),兩者將進(jìn)行合并,得到 160k 連續(xù)空閑空間。同時(shí) E 是最靠近堆頂?shù)目臻g, glibc 的 free 實(shí)現(xiàn)中,只要堆頂附近釋放總空間(包括合并的空間)超過(guò) 128k ,即會(huì)調(diào)用 sbrk(-SIZE) 來(lái)回溯堆頂指針,將原堆頂空間還給 OS ,如圖 (6) 所示。而堆內(nèi)的空閑空間還是不會(huì)歸還 OS 的。

由此可見(jiàn):

  1. malloc 使用 mmap 分配的內(nèi)存 ( 大于 128k) , free 會(huì)調(diào)用 munmap 系統(tǒng)調(diào)用馬上還給 OS ,實(shí)現(xiàn)真正釋放。

  2. 堆內(nèi)的內(nèi)存,只有釋放堆頂?shù)目臻g,同時(shí)堆頂總連續(xù)空閑空間大于 128k 才使用 sbrk(-SIZE) 回收內(nèi)存,真正歸還 OS 。

  3. 堆內(nèi)的空閑空間,是不會(huì)歸還給 OS 的。

六. 程序代碼中 malloc 的內(nèi)存都有相應(yīng)的 free ,就不會(huì)出現(xiàn)內(nèi)存泄露了嗎?

狹義上的內(nèi)存泄露是指 malloc 的內(nèi)存,沒(méi)有 free ,導(dǎo)致內(nèi)存浪費(fèi),直到程序結(jié)束。而廣義上的內(nèi)存泄露就是進(jìn)程使用內(nèi)存量不斷增加,或大大超出系統(tǒng)原設(shè)計(jì)的上限。

上一節(jié)說(shuō)到, free 了的內(nèi)存并不會(huì)馬上歸還 OS ,并且堆內(nèi)的空洞(碎片)更是很難真正釋放,除非空洞成為了新的堆頂。所以,如上一例子情況 (5) ,釋放了 40k 和 60k 兩片內(nèi)存,但如果此時(shí)需要申請(qǐng)大于 60k (如 70k ),沒(méi)有可用碎片,必須向 OS 申請(qǐng),實(shí)際使用內(nèi)存仍然增大。

因此,隨著系統(tǒng)頻繁地 malloc 和 free ,尤其對(duì)于小塊內(nèi)存,堆內(nèi)將產(chǎn)生越來(lái)越多不可用的碎片,導(dǎo)致“內(nèi)存泄露”。而這種“泄露”現(xiàn)象使用 valgrind 是無(wú)法檢測(cè)出來(lái)的。

下圖是 MySQL 存在大量分區(qū)表時(shí)的內(nèi)存使用情況 (RSS 和 VSZ) ,疑似“內(nèi)存泄露”。

因此,當(dāng)我們寫(xiě)程序時(shí),不能完全依賴 glibc 的 malloc 和 free 的實(shí)現(xiàn)。更好方式是建立屬于進(jìn)程的內(nèi)存池,即一次分配 (malloc) 大塊內(nèi)存,小內(nèi)存從內(nèi)存池中獲得,當(dāng)進(jìn)程結(jié)束或該塊內(nèi)存不可用時(shí),一次釋放 (free) ,可大大減少碎片的產(chǎn)生。

七. 既然堆內(nèi)內(nèi)存不能直接釋放,為什么不全部使用 mmap 來(lái)分配?

由于堆內(nèi)碎片不能直接釋放,而問(wèn)題 5 中說(shuō)到 mmap 分配的內(nèi)存可以會(huì)通過(guò) munmap 進(jìn)行 free ,實(shí)現(xiàn)真正釋放。既然堆內(nèi)碎片不能直接釋放,導(dǎo)致疑似“內(nèi)存泄露”問(wèn)題,為什么 malloc 不全部使用 mmap 來(lái)實(shí)現(xiàn)呢?而僅僅對(duì)于大于 128k 的大塊內(nèi)存才使用 mmap ?

其實(shí),進(jìn)程向 OS 申請(qǐng)和釋放地址空間的接口 sbrk/mmap/munmap 都是系統(tǒng)調(diào)用,頻繁調(diào)用系統(tǒng)調(diào)用都比較消耗系統(tǒng)資源的。并且, mmap 申請(qǐng)的內(nèi)存被 munmap 后,重新申請(qǐng)會(huì)產(chǎn)生更多的缺頁(yè)中斷。例如使用 mmap 分配 1M 空間,第一次調(diào)用產(chǎn)生了大量缺頁(yè)中斷 (1M/4K 次 ) ,當(dāng) munmap 后再次分配 1M 空間,會(huì)再次產(chǎn)生大量缺頁(yè)中斷。缺頁(yè)中斷是內(nèi)核行為,會(huì)導(dǎo)致內(nèi)核態(tài) CPU 消耗較大。另外,如果使用 mmap 分配小內(nèi)存,會(huì)導(dǎo)致地址空間的分片更多,內(nèi)核的管理負(fù)擔(dān)更大。

而堆是一個(gè)連續(xù)空間,并且堆內(nèi)碎片由于沒(méi)有歸還 OS ,如果可重用碎片,再次訪問(wèn)該內(nèi)存很可能不需產(chǎn)生任何系統(tǒng)調(diào)用和缺頁(yè)中斷,這將大大降低 CPU 的消耗。

因此, glibc 的 malloc 實(shí)現(xiàn)中,充分考慮了 sbrk 和 mmap 行為上的差異及優(yōu)缺點(diǎn),默認(rèn)分配大塊內(nèi)存 (128k) 才使用 mmap 獲得地址空間,也可通過(guò) mallopt(M_MMAP_THRESHOLD, <SIZE>)來(lái)修改這個(gè)臨界值。

八. 如何查看進(jìn)程的缺頁(yè)中斷信息?

可通過(guò)以下命令查看缺頁(yè)中斷信息

ps -o majflt,minflt -C <program_name>
ps -o majflt,minflt -p <pid>

其中, majflt 代表 major fault ,指大錯(cuò)誤, minflt 代表 minor fault ,指小錯(cuò)誤。這兩個(gè)數(shù)值表示一個(gè)進(jìn)程自啟動(dòng)以來(lái)所發(fā)生的缺頁(yè)中斷的次數(shù)。其中 majflt 與 minflt 的不同是, majflt 表示需要讀寫(xiě)磁盤(pán),可能是內(nèi)存對(duì)應(yīng)頁(yè)面在磁盤(pán)中需要 load 到物理內(nèi)存中,也可能是此時(shí)物理內(nèi)存不足,需要淘汰部分物理頁(yè)面至磁盤(pán)中。

例如,下面是 mysqld 的一個(gè)例子。

mysql@ TLOG_590_591:~> ps -o majflt,minflt -C mysqld
MAJFLT MINFLT
144856 15296294

如果進(jìn)程的內(nèi)核態(tài) CPU 使用過(guò)多,其中一個(gè)原因就可能是單位時(shí)間的缺頁(yè)中斷次數(shù)多個(gè),可通過(guò)以上命令來(lái)查看。

如果 MAJFLT 過(guò)大,很可能是內(nèi)存不足。

如果 MINFLT 過(guò)大,很可能是頻繁分配 / 釋放大塊內(nèi)存 (128k) , malloc 使用 mmap 來(lái)分配。對(duì)于這種情況,可通過(guò) mallopt(M_MMAP_THRESHOLD, <SIZE>)增大臨界值,或程序?qū)崿F(xiàn)內(nèi)存池。

九. 如何查看堆內(nèi)內(nèi)存的碎片情況?

glibc 提供了以下結(jié)構(gòu)和接口來(lái)查看堆內(nèi)內(nèi)存和 mmap 的使用情況。

struct mallinfo {
  int arena;    /* non-mmapped space allocated from system */
  int ordblks;  /* number of free chunks */
  int smblks;   /* number of fastbin blocks */
  int hblks;    /* number of mmapped regions */
  int hblkhd;   /* space in mmapped regions */
  int usmblks;  /* maximum total allocated space */
  int fsmblks;  /* space available in freed fastbin blocks */
  int uordblks; /* total allocated space */
  int fordblks; /* total free space */
  int keepcost; /* top-most, releasable (via malloc_trim) space */
};


/* 返回 heap(main_arena) 的內(nèi)存使用情況,以 mallinfo 結(jié)構(gòu)返回 */
struct mallinfo mallinfo();
/* 將 heap 和 mmap 的使用情況輸出到 stderr  */
void malloc_stats();

可通過(guò)以下例子來(lái)驗(yàn)證 mallinfo 和 malloc_stats 輸出結(jié)果。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <malloc.h>
size_t  heap_malloc_total, heap_free_total,
                mmap_total, mmap_count;
void print_info()
{
        struct mallinfo mi = mallinfo();
        printf("count by itself:\n");
        printf("\theap_malloc_total=%lu heap_free_total=%lu heap_in_use=%lu\n\
\tmmap_total=%lu mmap_count=%lu\n",
                heap_malloc_total*1024, heap_free_total*1024, heap_malloc_total*1024 - heap_free_total*1024,
                mmap_total*1024, mmap_count);
        printf("count by mallinfo:\n");
        printf("\theap_malloc_total=%lu heap_free_total=%lu heap_in_use=%lu\n\
\tmmap_total=%lu mmap_count=%lu\n",
                mi.arena, mi.fordblks, mi.uordblks,
                mi.hblkhd, mi.hblks);
      printf("from malloc_stats:\n");
        malloc_stats();
}

#define ARRAY_SIZE 200
int main(int argc, char** argv)
{
        char** ptr_arr[ARRAY_SIZE];
        int i;
        for( i = 0; i < ARRAY_SIZE; i++) {
                ptr_arr[i] = malloc(i * 1024);
                if ( i < 128)
                        heap_malloc_total += i;
                else {
                        mmap_total += i;
                        mmap_count++;
                }

        }
        print_info();
        for( i = 0; i < ARRAY_SIZE; i++) {
                if ( i % 2 == 0)
                        continue;
                free(ptr_arr[i]);
                if ( i < 128)
                        heap_free_total += i;
                else {
                        mmap_total -= i;
                        mmap_count--;
                }
        }
        printf("\nafter free\n");
        print_info();
        return 1;
}

該例子第一個(gè)循環(huán)為指針數(shù)組每個(gè)成員分配索引位置 (KB) 大小的內(nèi)存塊,并通過(guò) 128 為分界分別對(duì) heap 和 mmap 內(nèi)存分配情況進(jìn)行計(jì)數(shù);第二個(gè)循環(huán)是 free 索引下標(biāo)為奇數(shù)的項(xiàng),同時(shí)更新計(jì)數(shù)情況。通過(guò)程序的計(jì)數(shù)與 mallinfo/malloc_stats 接口得到結(jié)果進(jìn)行對(duì)比,并通過(guò) print_info 打印到終端。

下面是一個(gè)執(zhí)行結(jié)果:

count by itself:
        heap_malloc_total=8323072 heap_free_total=0 heap_in_use=8323072
        mmap_total=12054528 mmap_count=72
count by mallinfo:
        heap_malloc_total=8327168 heap_free_total=2032 heap_in_use=8325136
        mmap_total=12238848 mmap_count=72
from malloc_stats:
Arena 0:
system bytes     =    8327168
in use bytes     =    8325136
Total (incl. mmap):
system bytes     =   20566016
in use bytes     =   20563984
max mmap regions =         72
max mmap bytes   =   12238848

after free

count by itself:
        heap_malloc_total=8323072 heap_free_total=4194304 heap_in_use=4128768
        mmap_total=6008832 mmap_count=36
count by mallinfo:
        heap_malloc_total=8327168 heap_free_total=4197360 heap_in_use=4129808
        mmap_total=6119424 mmap_count=36
from malloc_stats:
Arena 0:
system bytes     =    8327168
in use bytes     =    4129808
Total (incl. mmap):
system bytes     =   14446592
in use bytes     =   10249232
max mmap regions =         72
max mmap bytes   =   12238848

由上可知,程序統(tǒng)計(jì)和 mallinfo 得到的信息基本吻合,其中 heap_free_total 表示堆內(nèi)已釋放的內(nèi)存碎片總和。

如果想知道堆內(nèi)片究竟有多碎 ,可通過(guò) mallinfo 結(jié)構(gòu)中的 fsmblks 、 smblks 、 ordblks 值得到,這些值表示不同大小區(qū)間的碎片總個(gè)數(shù),這些區(qū)間分別是 0~80 字節(jié), 80~512 字節(jié), 512~128k 。如果 fsmblks 、 smblks 的值過(guò)大,那碎片問(wèn)題可能比較嚴(yán)重了。

不過(guò), mallinfo 結(jié)構(gòu)有一個(gè)很致命的問(wèn)題,就是其成員定義全部都是 int ,在 64 位環(huán)境中,其結(jié)構(gòu)中的 uordblks/fordblks/arena/usmblks 很容易就會(huì)導(dǎo)致溢出,應(yīng)該是歷史遺留問(wèn)題,使用時(shí)要注意!

十. 除了 glibc 的 malloc/free ,還有其他第三方實(shí)現(xiàn)嗎?

其實(shí),很多人開(kāi)始詬病 glibc 內(nèi)存管理的實(shí)現(xiàn),就是在高并發(fā)性能低下和內(nèi)存碎片化問(wèn)題都比較嚴(yán)重,因此,陸續(xù)出現(xiàn)一些第三方工具來(lái)替換 glibc 的實(shí)現(xiàn),最著名的當(dāng)屬 google 的 tcmalloc 和 facebook 的 jemalloc 。

“Linux虛擬內(nèi)存管理方法是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI