深入理解Go語言 07 記憶體分配原理

2022-01-30 15:42:46 字數 2972 閱讀 1219

在說明golang記憶體分配之前,先了解下linux系統記憶體相關的基礎知識,有助於理解golang記憶體分配原理。

在早期記憶體管理中,如果程式太大,超過了空閒記憶體容量,就沒有辦法把全部程式裝入到記憶體,這時怎麼辦? 在許多年前,人們採用了一種叫做覆蓋技術,這樣一種解決方案。

這是一種什麼樣的解決方案?

就是把程式分為若干個部分,稱為覆蓋塊(overlay),核心思想就是分解(跟現代架構技術中分解、分模組思想很相近)。然後只把那些需要用到的指令和資料儲存在記憶體中,而把其餘的指令和資料儲存在記憶體外。關鍵是需要程式設計師手動來分塊。

這種技術有什麼問題呢?

這種技術必須由程式設計師手工把乙個大的程式劃分為若干個小的功能模組,並確定各個模組之間的呼叫關係。手工做這種事情很費時費力,使得程式設計複雜度增加。但是,程式設計師總是愛「偷懶」的,於是,人們去尋找更好的方案。

這個方案就是虛擬記憶體技術,它的基本思路:

程式執行程序的總大小可以超過實際可用的物理記憶體的大小。每個程序都可以有自己獨立的虛擬位址空間。然後通過cpu和mmu把虛擬記憶體位址轉換為實際實體地址。

這個就相當於在物理記憶體和程式之間增加了乙個中間層,虛擬記憶體。

虛擬儲存也可以看作是對記憶體的一種抽象。而且這種抽象帶來諸多好處:

它將記憶體看成是乙個儲存在磁碟上的位址空間的快取記憶體,在記憶體中只保留了活動區域,可以根據需要在磁碟和記憶體間來回傳送資料,高效使用記憶體。

它為每個程序提供了一致的位址空間,簡化了儲存的管理。

對程序起到保護作用,不被其他程序位址空間破壞,因為每個程序的位址空間都是相互獨立。

(程式:靜態的程式;程序:動態的,可以看作是程式的乙個例項)

壞處:就是複雜度進一步增加,這也是必然的。不過相比帶來的好處,複雜度的增加還是可以接受,並克服。

linux中對程序的處理抽象成了乙個結構體task_struct,我前面文章有對這個結構體的介紹。下面就看看程序的記憶體。

程序內存在linux(32位)中的布局:

來自:

最高位的1gb是linux核心空間,使用者**不能寫,否則觸發段錯誤。下面的3gb是程序使用的記憶體。

1、未能釋放已經不再使用的記憶體 - 記憶體洩漏

2、指向不可用的記憶體指標 - 野指標

3、指標所指向的物件已經被**了,但是指向該物件的指標仍舊指向已經**的記憶體位址 - 懸掛指標

4、分配或釋放記憶體太快或者太慢

5、分配記憶體大小不合理,造成記憶體碎片問題

6、記憶體碎片問題

可以檢視前面的文章 tcmalloc記憶體分配簡析,tcmalloc記憶體分配器的原理和golang記憶體分配器原理相近,所以理解了tcmalloc,golang記憶體分配原理也就理解大半,不過golang對它也有一些改動。

golang是怎麼解決的記憶體管理中的常見問題的呢?

針對上面的1、2、3 這三種問題,golang使用自動垃圾**機制,一般情況下,都不使用指標運算(要運算用unsafe包),很少的指標使用。當然,記憶體洩漏問題不能完全**,但是可以解決一大部分問題。

針對下面的4、5、6 這三種問題,golang採用了多級快取,預分配的方法,來加快記憶體分配和釋放**,儘量減少記憶體碎片。詳見 tcmalloc記憶體分配簡析 。

核心已經有乙個malloc的記憶體分配器,為什麼還有重寫乙個記憶體分配器?

可以看到,malloc是乙個很悠久的記憶體分配器,但是隨著時代的發展,多核多執行緒已經普及,為了更好的應用多執行緒,提高程式效率,以及改進記憶體碎片,所以重新寫了乙個記憶體分配器。從這裡 tcmalloc記憶體分配簡析 可以看出tcmaloc的優點,它將記憶體劃分為多級別,減少鎖的開銷。而且每個執行緒的快取又分開了多個小的物件,以減少記憶體碎片。等等優化改進。

所以go記憶體分配也繼承了這些優點。go還有乙個原因,那就是go還有gc,需要配合記憶體的垃圾**。

從上面的程序記憶體布局圖,可以看出乙個程序的記憶體劃分了好多不同的區域,而記憶體管理主要管理的就是stack和heap,其中stack (棧)區主要由編譯器和系統管理,程式語言主要管理heap(堆),主要是語言的runtime來管理。而且這裡的程序記憶體指的是虛擬記憶體。

golang記憶體分配的基本思想來自tcmalloc,所以go記憶體分配中的幾個概念與tcmalloc很相似,可以看看tcmalloc 中的概念 。

mspan

mspan跟tcmalloc中的span相似,它是golang記憶體管理中的基本單位,也是由頁組成的,每個頁大小為8kb,與tcmalloc中span組成的預設基本記憶體單位頁大小相同。mspan裡面按照8*2n大小(8b,16b,32b .... ),每乙個mspan又分為多個object。

就連名字也很像,mspan中的m應該是memory的第乙個字母。

mcache

mcache跟tcmalloc中的threadcache相似,threadcache為每個執行緒的cache,同理,mcache可以為golang中每個processor提供記憶體cache使用,每乙個mcache的組成單位也是mspan。

mcentral

mcentral跟tcmalloc中的centralcache相似,當mcache中空間不夠用,可以向mcentral申請記憶體。可以理解為mcentral為mcache的乙個「快取庫」,供mcaceh使用。它的記憶體組成單位也是mspan。

mcentral裡有兩個雙向鍊錶,乙個鍊錶表示還有空閒的mspan待分配,乙個表示煉表裡的mspan都被分配了。

mheap

mheap跟tcmalloc中的pageheap相似,負責大記憶體的分配。當mcentral記憶體不夠時,可以向mheap申請。那mheap沒有記憶體資源呢?跟tcmalloc一樣,向os作業系統申請。

還有,大於32kb的記憶體,也是直接向mheap申請。

總結

後面再進一步分析golang的記憶體分配原理。

深入理解C語言陣列與記憶體分配

很多c語言教材都提到陣列長度的定義必須是常量,為什麼c語言陣列會有這種限制呢?這就要從程式變數的記憶體分配開始說起了。我麼知道程式在執行時候資料 變數可能會存放的段有以下幾個 堆區 malloc分配的記憶體就在這個區中 棧區 程式呼叫時函式內部的區域性變數在這個區中 data區 這個區中的資料是程式...

深入理解Android EventBus原理

1.定義乙個evnet public static class messageevent2.準備觀察者 宣告和注釋你的訂閱方法,可選地指定執行緒模式 subscribe threadmode threadmode.main 比如這個就指定主線程 關於型別的解釋介紹請查閱 註冊和反註冊在你的使用中,例...

深入理解C語言 深入理解記憶體四區

當陣列做函式引數的時候,會退化為乙個指標 此時在函式內是得不到陣列大小的 因此,陣列做函式引數的時候需要傳遞陣列大小,也就是多傳遞乙個引數 void func int arr,int num 若存在以上函式,c c 編譯器在編譯的時候,會將陣列優化為乙個指標,指向陣列的首位址,因此無法通過sizeo...