最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文搞懂Go語言堆內存原理小結

 更新時間:2025年10月28日 10:39:21   作者:數(shù)據(jù)知道  
堆內存是程序運行時動態(tài)分配的內存區(qū)域,與棧內存形成對比,本文就來詳細的介紹一下Go語言堆內存原理小結, 具有一定的參考價值,感興趣的可以了解一下

一、基本概念理解

1.1 什么是堆內存?

堆內存是程序內存中用于動態(tài)內存分配的部分。堆內存不是在編譯過程中預先確定的,而是在程序運行過程中動態(tài)管理的。程序在執(zhí)行過程中可以根據(jù)需要從堆中申請、釋放內存。

在繼續(xù)介紹之前,試著了解一下進程的內存布局,如下圖所示,可以簡單了解大致的內存布局。

+ - - - - - - - - - - - - - - - +
| Stack                         | ←- 棧,靜態(tài)分配
| - - - - - - - - - - - - - - - | 
| Heap                          | ←- 堆,動態(tài)分配
| - - - - - - - - - - - - - - - | 
| Uninitialized Data            | ←- 未初始化數(shù)據(jù)
| - - - - - - - - - - - - - - - | 
| Initialized Data              | ←- 初始化數(shù)據(jù)
| - - - - - - - - - - - - - - - | 
| Code                          | ←- 代碼(文本段)
+ - - - - - - - - - - - - - - - +

                     進程內存布局

我們來分解一下進程的內存布局,看看它們是如何協(xié)同工作的:

  • 棧(Stack):這部分內存用于靜態(tài)內存分配,是存儲局部變量和函數(shù)調用信息的地方,會隨著函數(shù)的調用和返回而自動增大和縮小。
  • 堆(Heap):這是動態(tài)內存分配區(qū)域。當程序需要申請未預先定義的內存時,就會向堆申請空間。這里的內存可以在運行時分配和釋放,為程序提供了處理數(shù)組、鏈表等動態(tài)數(shù)據(jù)結構所需的靈活性。
  • 未初始化數(shù)據(jù)(BSS 段):該段存放開發(fā)者已聲明但并未初始化的全局變量和靜態(tài)變量。程序啟動時,操作系統(tǒng)會將這些變量初始化為零。
  • 初始化數(shù)據(jù):該區(qū)域包含開發(fā)者已初始化的全局變量和靜態(tài)變量。程序一開始運行,這些變量就可以立即使用。
  • 代碼(文本段):該段存儲程序的可執(zhí)行指令。通常這部分內存是只讀的,以防止意外修改程序指令。

1.2 堆內存的特點

動態(tài)分配:內存在運行時申請、釋放。
可變大?。悍峙涞膬却娲笮】梢宰兓?。
基于指針的管理:使用指針訪問和控制內存。

+ - - - - - - - - - - -+
| Heap Memory.         | ←- 堆內存
| - - - - - - - - - - -| 
| Free Block           | ←- 空閑塊
| - - - - - - - - - - -| 
| Allocated Block 1    | ←- 已分配塊1
| [Pointer -> Data]    |
| - - - - - - - - - - -| 
| Free Block           | ←- 空閑塊
| - - - - - - - - - - -| 
| Allocated Block 2    | ←- 已分配塊2
| [Pointer -> Data]    |
| - - - - - - - - - - -| 
| Free Block.          | ←- 空閑塊
+ - - - - - - - - - - -+

                   動態(tài)分配
  • 空閑塊(Free Blocks):這些是當前未分配的內存塊,可供將來使用。當程序請求內存時,可以從這些空閑塊中獲取。
  • 已分配塊(Allocated Blocks):這些部分已分配給程序并儲存了數(shù)據(jù)。每個已分配塊通常都包含一個指向其所含數(shù)據(jù)的指針。

多個空閑塊和已分配塊的存在表明,內存的分配和釋放在程序運行過程中不斷發(fā)生。由于內存分配和釋放的時間不同,導致空閑內存段和已用內存段交替出現(xiàn),堆就會出現(xiàn)這種碎片化現(xiàn)象。

1.3 前置知識:棧與堆的根本區(qū)別

要理解堆,必先理解棧。在 Go 程序中,每個 Goroutine 都有一個獨立的,而所有 Goroutine 共享一個。

特性
所有權Goroutine 獨有進程內所有 Goroutine 共享
分配與釋放編譯器/運行時自動管理,函數(shù)入棧時分配,出棧時釋放,速度極快垃圾回收器管理,分配相對較慢,釋放時機不確定(GC時)
大小小而固定(初始幾KB,可動態(tài)增長,但有上限)非常大(可達 TB 級別,受限于系統(tǒng)內存)
存儲數(shù)據(jù)函數(shù)參數(shù)、局部變量、返回地址等生命周期明確的數(shù)據(jù)生命周期不確定的數(shù)據(jù),比如函數(shù)返回后仍需被訪問的對象
訪問速度快(連續(xù)內存,CPU緩存友好)慢(內存不連續(xù),可能涉及系統(tǒng)調用)

1.4 堆內存如何工作?

堆內存由操作系統(tǒng)管理。當程序請求內存時,操作系統(tǒng)會從進程的堆內存段中分配內存。這一過程涉及多個關鍵組件和功能:

主要組成部分:

  • 堆內存段:進程內存中保留用于動態(tài)分配的部分
  • mmap:調整數(shù)據(jù)段末尾以增加或減少堆大小的系統(tǒng)調用
  • malloc 和 free:C 庫提供的函數(shù),用于分配和釋放堆上的內存
  • 內存管理器:C 庫的一個組件,用于管理堆,跟蹤已分配和已釋放的內存塊。

1.5 核心原理:Go 對象如何分配到堆上?

當一個 Goroutine 需要在堆上分配內存時,流程如下:

  1. 獲取 P:該 Goroutine 綁定到某個 P(邏輯處理器)上。
  2. 查找 mcache:從 P 中獲取其專屬的內存緩存 mcache。
  3. 大小分級:根據(jù)要分配的對象大小,選擇合適的規(guī)格:
    • 微小對象 (< 16 bytes):直接在 mcache 中用一個專門的 tiny 對象來處理,避免浪費。
    • 小對象 (< 32KB):在 mcache 中尋找對應大小規(guī)格的 mspan(內存跨度)來分配。
    • 大對象 (>= 32KB):直接跳過 mcachemcentral,向全局的 mheap 申請內存。
      關鍵點:絕大多數(shù)對象都是小對象,它們的分配都可以在 mcache 中無鎖完成,速度極快。

核心問題:編譯器如何決定一個對象放棧上還是堆上?
答案是:逃逸分析。

Go 編譯器會分析代碼,如果一個局部變量的作用域超出了它所在的函數(shù)(即“逃逸”了),那么它就必須被分配在堆上。如:

// 情況一:不逃逸,分配在棧上
func stackExample() int {
    x := 10  // x 的生命周期只在 stackExample 函數(shù)內
    return x
}
// 情況二:逃逸,分配在堆上
func heapExample() *int {
    x := 10  // x 的指針被返回,作用域超出了函數(shù),x 逃逸到堆上
    return &x
}

可以使用 go build -gcflags="-m" 命令來查看逃逸分析的結果:

$ go build -gcflags="-m" main.go
# command-line-arguments
./main.go:10:6: can inline heapExample
./main.go:11:2: leaking param: x
./main.go:11:2: moved to heap: x

moved to heap: x 明確告訴我們變量 x 被分配到了堆上。

1.6 Go 內存分配器:TCMalloc 的繼承與演進

Go 的內存分配器高度借鑒了 Google 的 TCMalloc。其核心思想是:避免多線程競爭,將內存管理工作分攤到每個處理器(P)上。
這帶來了兩個核心設計:

  1. 無鎖化:每個 P 都有自己的本地內存緩存,大部分分配操作都在 P 內部完成,無需加鎖。
  2. 分級管理:將內存按大小分為不同級別,用不同策略管理,提高分配效率。

二、Go 如何管理堆內存

Go 為堆內存管理提供了內置函數(shù)和數(shù)據(jù)結構,如 new、make、slices、maps 和 channels。這些函數(shù)和數(shù)據(jù)結構抽象掉了底層細節(jié),在內部與操作系統(tǒng)的內存管理機制進行了交互。

2.1 案例

我們通過一個簡單的 Go 程序來理解,該程序為整數(shù)片段分配內存、初始化數(shù)值并打印。(main.go)

package main

import (
    "fmt"
    "runtime"
)

func main() {
    // 為包含10個整數(shù)的切片分配內存(動態(tài)數(shù)組)
    memorySize := 10
    slice := make([]int, memorySize)

    // 初始化并使用分配的內存
    for i := 0; i < len(slice); i++ {
        slice[i] = 5 // 為每個元素賦值
    }

    // 打印值
    for i := 0; i < len(slice); i++ {
        fmt.Printf("%d ", slice[i])
    }
    fmt.Println()

    // 通過強制垃圾收集演示內存釋放
    runtime.GC()
}

為了了解 Go 如何與 Linux 內存管理庫交互,可以使用 strace(centos系統(tǒng)可通過:yum install strace安裝)來跟蹤 Go 程序進行的系統(tǒng)調用。

2.2 內存分配中的系統(tǒng)調用

$ go build -o memory_allocation main.go
$ strace -f -e trace=mmap,munmap ./memory_allocation

執(zhí)行結果如下:

mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f50caa6b000
mmap(NULL, 131072, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f50caa4b000
mmap(NULL, 1048576, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f50ca94b000
mmap(NULL, 8388608, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f50ca14b000
mmap(NULL, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f50c614b000
mmap(NULL, 536870912, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f50a614b000
mmap(NULL, 536870912, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f508614b000
mmap(0xc000000000, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xc000000000
mmap(NULL, 33554432, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f508414b000
mmap(NULL, 69648, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5084139000
mmap(0xc000000000, 4194304, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xc000000000
mmap(0x7f50caa4b000, 131072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f50caa4b000
mmap(0x7f50ca9cb000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f50ca9cb000
mmap(0x7f50ca551000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f50ca551000
mmap(0x7f50c817b000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f50c817b000
mmap(0x7f50b62cb000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f50b62cb000
mmap(0x7f50962cb000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f50962cb000
mmap(NULL, 1048576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5084039000
mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5084029000
mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5084019000
mmap(NULL, 1439992, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5083eb9000
strace: Process 1425438 attached
[pid 1425437] mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5083e79000
strace: Process 1425439 attached
strace: Process 1425440 attached
[pid 1425437] mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5083e39000
strace: Process 1425441 attached
5 5 5 5 5 5 5 5 5 5 
[pid 1425437] mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5083e29000
[pid 1425440] +++ exited with 0 +++
[pid 1425439] +++ exited with 0 +++
[pid 1425438] +++ exited with 0 +++
[pid 1425441] +++ exited with 0 +++
+++ exited with 0 +++
+ - - - - - - - - - - -+
| Go Program           | ←- Go 程序
| - - - - - - - - - - -| 
| Calls Go Runtime     | ←- 調用 Go 運行時
| - - - - - - - - - - -| 
| Uses syscalls:       | ←- 系統(tǒng)調用:mmap,munmap
| mmap, munmap         |
| - - - - - - - - - - -| 
| Interacts with OS    | ←- 與操作系統(tǒng)內存管理器交互
| Memory Manager       |
+ - - - - - - - - - - -+
                      系統(tǒng)調用的簡化示例

strace 輸出解釋

  • mmap 調用:mmap 系統(tǒng)調用用于分配內存頁。輸出中的每個 mmap 調用都是請求操作系統(tǒng)分配特定數(shù)量(用 size 參數(shù)指定,例如 262144、131072 字節(jié))的內存,。
  • 內存保護(Memory Protections):參數(shù) PROT_READ|PROT_WRITE 表示分配的內存應是可讀和可寫的。
  • 匿名映射(Anonymous Mapping):MAP_PRIVATE|MAP_ANONYMOUS 標記表示內存沒有任何文件支持,所做更改對進程來說是私有的。
  • 固定地址映射(Fixed Address Mapping):有些 mmap 調用使用 MAP_FIXED 標記,指定內存應映射到特定地址,通常用于直接管理特定內存區(qū)域。

2.3 內存分配過程的各個階段:

+ - - - - - - - - - - -+
| Initialize Slice     | ←- 初始化切片
| [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] |
| - - - - - - - - - - -|
| Set Values           | ←- 設置值
| [5, 5, 5, 5, 5, 5, 5, 5, 5, 5] |
| - - - - - - - - - - -| 
| Print Values         | ←- 打印值
| 5 5 5 5 5 5 5 5 5 5  |
| - - - - - - - - - - -| 
| Force GC             | ←- 強制垃圾回收
| - - - - - - - - - - -|

上圖說明了 Go 動態(tài)內存分配和管理的逐步過程。

  • 1、初始化切片[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],切片(動態(tài)數(shù)組)的初始狀態(tài)為 10 個元素,全部設置為 0。這一步展示了 Go 如何為切片分配內存。
  • 2、設置值[5, 5, 5, 5, 5, 5, 5, 5, 5, 5] ,然后,在切片的每個元素中填入值 5。這一步演示了如何初始化和使用分配的內存。
  • 3、打印值5 5 5 5 5 5 5 5 5 5,打印切片的值,確認內存分配和初始化成功。這一步驗證程序是否正確訪問和使用了分配的內存。
  • 4、強制 GC(垃圾回收):手動觸發(fā)垃圾回收器,釋放不再使用的內存。這一步強調 Go 的自動內存管理和清理過程,確保了資源的有效利用。

三、 堆內存管理:三級結構

Go 的堆內存管理是一個精密的三級(或四級)結構,理解它就理解了 Go 內存管理的核心。

3.1 第一級:mcache (Per-P Cache)

  • 是什么:每個 P 都有一個獨立的 mcache。
  • 作用:存儲各種大小規(guī)格的 mspan 的空閑列表。
  • 特點無鎖分配。因為 P 同一時間只能被一個 Goroutine 占用,所以從 mcache 分配內存是線程安全的,無需加鎖。這是 Go 高并發(fā)內存分配性能高的關鍵。

3.2 第二級:mcentral (Central Cache)

  • 是什么:全局的內存中心,所有 P 共享。它按 spanclass(大小規(guī)格)分為多個 mcentral。
  • 作用:當 mcache 中的 mspan 不夠用時,P 會向對應的 mcentral 申請新的 mspan。
  • 特點需要加鎖。因為多個 P 可能同時向同一個 mcentral 申請內存,所以需要加鎖保證線程安全。

3.3 第三級:mheap (Heap Manager)

  • 是什么:全局唯一的堆內存管理器,掌管著所有從操作系統(tǒng)申請來的大塊內存。
  • 作用
    1. 管理 mcentral,當 mcentralmspan 不足時,向 mheap 申請。
    2. 直接處理大對象(>= 32KB)的分配請求。
    3. 當內存不足時,向操作系統(tǒng)申請更多內存(調用 mmap)。

3.4 基礎單元:mspan (Memory Span)

  • 是什么mcache、mcentralmheap 之間流轉的基本單位。它是一段連續(xù)的內存地址,由多個頁組成。
  • 作用mspan 會被劃分為特定大小的塊,用于存儲同一種規(guī)格的對象。例如,一個 mspan 可能專門用來存放所有 16 字節(jié)大小的對象。

流程串聯(lián): Goroutine -> mcache (無鎖) -> mcentral (加鎖) -> mheap (全局鎖) -> OS

到此這篇關于一文搞懂Go語言堆內存原理小結的文章就介紹到這了,更多相關Go語言 堆內存內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 詳解Go語言中net/http包的使用

    詳解Go語言中net/http包的使用

    Go語言內置的?net/http?包十分的優(yōu)秀,提供了HTTP客戶端和服務端的實現(xiàn),本文主要就來和大家聊聊net/http包的使用,感興趣的可以了解一下
    2023-07-07
  • 詳解Golang中Requests包的使用

    詳解Golang中Requests包的使用

    Go的net/http包雖然功能強大、用途也廣告,但要想正確的使用請求的客戶端是非常繁瑣的,所以本文和大家分享一個高效的HTTP的請求包carlmjohnson/requests的使用,需要的小伙伴可以了解一下
    2023-06-06
  • go build 移除路徑信息的實現(xiàn)步驟

    go build 移除路徑信息的實現(xiàn)步驟

    本文主要介紹了go build 移除路徑信息的實現(xiàn)步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2025-11-11
  • 淺談golang中的&^位清空操作

    淺談golang中的&^位清空操作

    這篇文章主要介紹了淺談golang中的&^位清空操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Go變量作用域代碼實戰(zhàn)詳解

    Go變量作用域代碼實戰(zhàn)詳解

    Go語言提供了幾種不同的作用域類型,使得開發(fā)者可以靈活地控制變量的可見范圍和生命周期,本章節(jié)將詳細概述Go語言中變量的各種作用域,幫助讀者更好地理解和應用這些概念,需要的朋友可以參考下
    2024-06-06
  • Go語言字符串基礎示例詳解

    Go語言字符串基礎示例詳解

    這篇文章主要為大家介紹了Go語言字符串基礎的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2021-11-11
  • Go重寫http請求重定向的方法

    Go重寫http請求重定向的方法

    當使用Go語言進行 HTTP 請求時,默認情況下,http.Client 會自動處理服務器返回的重定向響應(3xx 狀態(tài)碼),本文將詳細介紹如何在 Go 中實現(xiàn)禁止 HTTP 請求的重定向、限制重定向次數(shù)以及添加自定義重定向策略,需要的朋友可以參考下
    2024-08-08
  • golang原生http包實現(xiàn)各種情況的get請求方式

    golang原生http包實現(xiàn)各種情況的get請求方式

    這篇文章主要介紹了golang原生http包實現(xiàn)各種情況的get請求方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • Go語言crypto包創(chuàng)建自己的密碼加密工具實現(xiàn)示例

    Go語言crypto包創(chuàng)建自己的密碼加密工具實現(xiàn)示例

    Go語言借助它的簡單性和強大的標準庫,實現(xiàn)一個自己的密碼加密工具,本文將會結合代碼示例深入探討如何使用Go語言的crypto包來實現(xiàn)自己的加密工具
    2023-11-11
  • 圖解Golang的GC垃圾回收算法

    圖解Golang的GC垃圾回收算法

    這篇文章主要介紹了圖解Golang的GC垃圾回收算法,詳細的介紹了三種經典的算法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-03-03

最新評論

珠海市| 新化县| 河北区| 辰溪县| 溧水县| 塔河县| 扎兰屯市| 鄂尔多斯市| 西盟| 达孜县| 海晏县| 高雄市| 锡林郭勒盟| 霸州市| 商丘市| 光泽县| 大渡口区| 隆安县| 宾阳县| 新竹市| 怀仁县| 宁都县| 宁乡县| 灵台县| 福清市| 葫芦岛市| 郑州市| 图木舒克市| 安顺市| 敦煌市| 延川县| 新闻| 佳木斯市| 滦平县| 永年县| 蛟河市| 类乌齐县| 永靖县| 新竹市| 孝感市| 武穴市|