<input id="ohw05"></input>
  • <table id="ohw05"><menu id="ohw05"></menu></table>
  • <var id="ohw05"></var>
  • <code id="ohw05"><cite id="ohw05"></cite></code>
    <label id="ohw05"></label>
    <var id="ohw05"></var>
  • 在 4GB 物理內存的機器上,申請 8G 內存會怎么樣?

    作者:小林coding

    計算機八股文刷題網站:https://xiaolincoding.com/

    大家好,我是小林。

    看到讀者在群里討論這些面試題:

    其中,第一個問題「在 4GB 物理內存的機器上,申請 8G 內存會怎么樣?」存在比較大的爭議,有人說會申請失敗,有的人說可以申請成功。

    這個問題在沒有前置條件下,就說出答案就是耍流氓。這個問題要考慮三個前置條件:

    • 操作系統是 32 位的,還是 64 位的?
    • 申請完 8G 內存后會不會被使用?
    • 操作系統有沒有使用 Swap 機制?

    所以,我們要分場景討論。

    操作系統虛擬內存大小

    應用程序通過 malloc 函數申請內存的時候,實際上申請的是虛擬內存,此時并不會分配物理內存。

    當應用程序讀寫了這塊虛擬內存,CPU 就會去訪問這個虛擬內存, 這時會發現這個虛擬內存沒有映射到物理內存, CPU 就會產生缺頁中斷,進程會從用戶態切換到內核態,并將缺頁中斷交給內核的 Page Fault Handler (缺頁中斷函數)處理。

    缺頁中斷處理函數會看是否有空閑的物理內存:

    • 如果有,就直接分配物理內存,并建立虛擬內存與物理內存之間的映射關系。
    • 如果沒有空閑的物理內存,那么內核就會開始進行回收內存的工作,如果回收內存工作結束后,空閑的物理內存仍然無法滿足此次物理內存的申請,那么內核就會放最后的大招了觸發 OOM (Out of Memory)機制。

    32 位操作系統和 64 位操作系統的虛擬地址空間大小是不同的,在 Linux 操作系統中,虛擬地址空間的內部又被分為內核空間和用戶空間兩部分,如下所示:

    通過這里可以看出:

    • 32 位系統的內核空間占用 1G,位于最高處,剩下的 3G 是用戶空間;
    • 64 位系統的內核空間和用戶空間都是 128T,分別占據整個內存空間的最高和最低處,剩下的中間部分是未定義的。

    現在可以回答這個問題了:在 32 位操作系統、4GB 物理內存的機器上,申請 8GB 內存,會怎么樣?

    因為 32 位操作系統,進程最多只能申請 3 GB 大小的虛擬內存空間,所以進程申請 8GB 內存的話,在申請虛擬內存階段就會失敗(我手上沒有 32 位操作系統測試,我估計失敗的原因是 OOM)。

    在 64 位操作系統、4GB 物理內存的機器上,申請 8G 內存,會怎么樣?

    64 位操作系統,進程可以使用 128 TB 大小的虛擬內存空間,所以進程申請 8GB 內存是沒問題的,因為進程申請內存是申請虛擬內存,只要不讀寫這個虛擬內存,操作系統就不會分配物理內存。

    我們可以簡單做個測試,我的服務器是 64 位操作系統,但是物理內存只有 2 GB:

    現在,我在機器上,連續申請 4 次 1 GB 內存,也就是一共申請了 4 GB 內存,注意下面代碼只是單純分配了虛擬內存,并沒有使用該虛擬內存:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>

    #define MEM_SIZE 1024 * 1024 * 1024

    int main() {
        char* addr[4];
        int i = 0;
        for(i = 0; i < 4; ++i) {
            addr[i] = (char*) malloc(MEM_SIZE);
            if(!addr[i]) {
                printf("執行 malloc 失敗, 錯誤:%s\n",strerror(errno));
              return -1;
            }
            printf("主線程調用malloc后,申請1gb大小得內存,此內存起始地址:0X%x\n", addr[i]);
        }
        
        //輸入任意字符后,才結束
        getchar();
        return 0;
    }

    然后運行這個代碼,可以看到,我的物理內存雖然只有 2GB,但是程序正常分配了 4GB 大小的虛擬內存:

    我們可以通過下面這條命令查看進程(test)的虛擬內存大小:

    # ps aux | grep test
    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root      7797  0.0  0.0 4198540  352 pts/1    S+   16:58   0:00 ./test

    其中,VSZ 就代表進程使用的虛擬內存大小,RSS 代表進程使用的物理內存大小。可以看到,VSZ 大小為 4198540,也就是 4GB 的虛擬內存。

    Swap 機制的作用

    前面討論在 32 位/64 位操作系統環境下,申請的虛擬內存超過物理內存后會怎么樣?

    • 在 32 位操作系統,因為進程最大只能申請 3 GB 大小的虛擬內存,所以直接申請 8G 內存,會申請失敗。
    • 在 64 位操作系統,因為進程最大只能申請 128 TB 大小的虛擬內存,即使物理內存只有 4GB,申請 8G 內存也是沒問題,因為申請的內存是虛擬內存。

    程序申請的虛擬內存,如果沒有被使用,它是不會占用物理空間的。當訪問這塊虛擬內存后,操作系統才會進行物理內存分配。

    如果申請物理內存大小超過了空閑物理內存大小,就要看操作系統有沒有開啟 Swap 機制:

    • 如果沒有開啟 Swap 機制,程序就會直接 OOM;
    • 如果有開啟 Swap 機制,程序可以正常運行。

    什么是 Swap 機制?

    當系統的物理內存不夠用的時候,就需要將物理內存中的一部分空間釋放出來,以供當前運行的程序使用。那些被釋放的空間可能來自一些很長時間沒有什么操作的程序,這些被釋放的空間會被臨時保存到磁盤,等到那些程序要運行時,再從磁盤中恢復保存的數據到內存中。

    另外,當內存使用存在壓力的時候,會開始觸發內存回收行為,會把這些不常訪問的內存先寫到磁盤中,然后釋放這些內存,給其他更需要的進程使用。再次訪問這些內存時,重新從磁盤讀入內存就可以了。

    這種,將內存數據換出磁盤,又從磁盤中恢復數據到內存的過程,就是 Swap 機制負責的。

    Swap 就是把一塊磁盤空間或者本地文件,當成內存來使用,它包含換出和換入兩個過程:

    • 換出(Swap Out) ,是把進程暫時不用的內存數據存儲到磁盤中,并釋放這些數據占用的內存;
    • 換入(Swap In),是在進程再次訪問這些內存的時候,把它們從磁盤讀到內存中來;

    Swap 換入換出的過程如下圖:

    使用 Swap 機制優點是,應用程序實際可以使用的內存空間將遠遠超過系統的物理內存。由于硬盤空間的價格遠比內存要低,因此這種方式無疑是經濟實惠的。當然,頻繁地讀寫硬盤,會顯著降低操作系統的運行速率,這也是 Swap 的弊端。

    Linux 中的 Swap 機制會在內存不足和內存閑置的場景下觸發:

    • 內存不足:當系統需要的內存超過了可用的物理內存時,內核會將內存中不常使用的內存頁交換到磁盤上為當前進程讓出內存,保證正在執行的進程的可用性,這個內存回收的過程是強制的直接內存回收(Direct Page Reclaim)。直接內存回收是同步的過程,會阻塞當前申請內存的進程。
    • 內存閑置:應用程序在啟動階段使用的大量內存在啟動后往往都不會使用,通過后臺運行的守護進程(kSwapd),我們可以將這部分只使用一次的內存交換到磁盤上為其他內存的申請預留空間。kSwapd 是 Linux 負責頁面置換(Page replacement)的守護進程,它也是負責交換閑置內存的主要進程,它會在空閑內存低于一定水位時,回收內存頁中的空閑內存保證系統中的其他進程可以盡快獲得申請的內存。kSwapd 是后臺進程,所以回收內存的過程是異步的,不會阻塞當前申請內存的進程。

    Linux 提供了兩種不同的方法啟用 Swap,分別是 Swap 分區(Swap Partition)和 Swap 文件(Swapfile),開啟方法可以看這個資料

    • Swap 分區是硬盤上的獨立區域,該區域只會用于交換分區,其他的文件不能存儲在該區域上,我們可以使用 Swapon -s 命令查看當前系統上的交換分區;
    • Swap 文件是文件系統中的特殊文件,它與文件系統中的其他文件也沒有太多的區別;

    Swap 換入換出的是什么類型的內存?

    內核緩存的文件數據,因為都有對應的磁盤文件,所以在回收文件數據的時候, 直接寫回到對應的文件就可以了。

    但是像進程的堆、棧數據等,它們是沒有實際載體,這部分內存被稱為匿名頁。而且這部分內存很可能還要再次被訪問,所以不能直接釋放內存,于是就需要有一個能保存匿名頁的磁盤載體,這個載體就是 Swap 分區。

    匿名頁回收的方式是通過 Linux 的 Swap 機制,Swap 會把不常訪問的內存先寫到磁盤中,然后釋放這些內存,給其他更需要的進程使用。再次訪問這些內存時,重新從磁盤讀入內存就可以了。

    接下來,通過兩個實驗,看看申請的物理內存超過物理內存會怎樣?

    • 實驗一:沒有開啟 Swap 機制
    • 實驗二:有開啟 Swap 機制

    實驗一:沒有開啟 Swap 機制

    我的服務器是 64 位操作系統,但是物理內存只有 2 GB,而且沒有 Swap 分區:

    我們改一下前面的代碼,使得在申請完 4GB 虛擬內存后,通過 memset 函數訪問這個虛擬內存,看看在沒有 Swap 分區的情況下,會發生什么?

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>

    #define MEM_SIZE 1024 * 1024 * 1024

    int main() {
        char* addr[4];
        int i = 0;
        for(i = 0; i < 4; ++i) {
            addr[i] = (char*) malloc(MEM_SIZE);
            if(!addr[i]) {
                printf("執行 malloc 失敗, 錯誤:%s\n",strerror(errno));
                return -1;
            }
            printf("主線程調用malloc后,申請1gb大小得內存,此內存起始地址:0X%x\n", addr[i]);
        }

        for(i = 0; i < 4; ++i) {
            printf("開始訪問第 %d 塊虛擬內存(每一塊虛擬內存為 1 GB)\n", i + 1);
            memset(addr[i], 0, MEM_SIZE);
        }
        
        //輸入任意字符后,才結束
        getchar();
        return 0;
    }

    運行結果:

    可以看到,在訪問第 2 塊虛擬內存(每一塊虛擬內存是 1 GB)的時候,因為超過了機器的物理內存(2GB),進程(test)被操作系統殺掉了。

    通過查看 message 系統日志,可以發現該進程是被操作系統 OOM killer 機制殺掉了,日志里報錯了 Out of memory,也就是發生 OOM(內存溢出錯誤)。

    什么是 OOM?

    內存溢出(Out Of Memory,簡稱OOM)是指應用系統中存在無法回收的內存或使用的內存過多,最終使得程序運行要用到的內存大于能提供的最大內存。此時程序就運行不了,系統會提示內存溢出。

    實驗二:有開啟 Swap 機制

    我用我的 mac book pro 筆記本做測試,我的筆記本是 64 位操作系統,物理內存是 8 GB, 目前 Swap 分區大小為 1 GB(注意這個大小不是固定不變的,Swap 分區總大小是會動態變化的,當沒有使用 Swap 分區時,Swap 分區總大小是 0;當使用了 Swap 分區,Swap 分區總大小會增加至 1 GB;當 Swap 分區已使用的大小超過 1 GB 時;Swap 分區總大小就會增加到至 2 GB;當 Swap 分區已使用的大小超過 2 GB 時;Swap 分區總大小就增加至 3GB,如此往復。這個估計是 macos 自己實現的,Linux 的分區則是固定大小的,Swap 分區不會根據使用情況而自動增長)。

    為了方便觀察磁盤 I/O 情況,我們改進一下前面的代碼,分配完 32 GB虛擬內存后(筆記本物理內存是 8 GB),通過一個 while 循環頻繁訪問虛擬內存,代碼如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    #define MEM_SIZE 32 * 1024 * 1024 * 1024

    int main() {
        char* addr = (char*) malloc((long)MEM_SIZE);
        printf("主線程調用malloc后,目前共申請了 32gb 的虛擬內存\n");
        
        //循環頻繁訪問虛擬內存
        while(1) {
              printf("開始訪問 32gb 大小的虛擬內存...\n");
              memset(addr, 0, (long)MEM_SIZE);
        }
        return 0;
    }

    運行結果如下:

    可以看到,在有 Swap 分區的情況下,即使筆記本物理內存是 8 GB,申請并使用 32 GB 內存是沒問題,程序正常運行了,并沒有發生 OOM。

    從下圖可以看到,進程的內存顯示 32 GB(這個不要理解為占用的物理內存,理解為已被訪問的虛擬內存大小,也就是在物理內存呆過的內存大小),系統已使用的 Swap 分區達到 2.3 GB。

    此時我的筆記本電腦的磁盤開始出現“沙沙”的聲音,通過查看磁盤的 I/O 情況,可以看到磁盤 I/O 達到了一個峰值,非常高:

    有了 Swap 分區,是不是意味著進程可以使用的內存是無上限的?

    當然不是,我把上面的代碼改成了申請 64GB 內存后,當進程申請完 64GB 虛擬內存后,使用到 56 GB (這個不要理解為占用的物理內存,理解為已被訪問的虛擬內存大小,也就是在物理內存呆過的內存大小)的時候,進程就被系統 kill 掉了,如下圖:

    當系統多次嘗試回收內存,還是無法滿足所需使用的內存大小,進程就會被系統 kill 掉了,意味著發生了 OOM (PS:我沒有在 macos 系統找到像 linux 系統里的 /var/log/message 系統日志文件,所以無法通過查看日志確認是否發生了 OOM)。

    總結

    至此, 驗證完成了。簡單總結下:

    • 在 32 位操作系統,因為進程最大只能申請 3 GB 大小的虛擬內存,所以直接申請 8G 內存,會申請失敗。
    • 在 64位 位操作系統,因為進程最大只能申請 128 TB 大小的虛擬內存,即使物理內存只有 4GB,申請 8G 內存也是沒問題,因為申請的內存是虛擬內存。如果這塊虛擬內存被訪問了,要看系統有沒有 Swap 分區:
      • 如果沒有 Swap 分區,因為物理空間不夠,進程會被操作系統殺掉,原因是 OOM(內存溢出);
      • 如果有 Swap 分區,即使物理內存只有 4GB,程序也能正常使用 8GB 的內存,進程可以正常運行;

    系列內存管理文章

    posted @ 2022-06-08 10:57  小林coding  閱讀(5545)  評論(9編輯  收藏  舉報
    国产美女a做受大片观看