欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文搞懂Go語言堆內(nèi)存原理小結(jié)

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

一、基本概念理解

1.1 什么是堆內(nèi)存?

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

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

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

                     進程內(nèi)存布局

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

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

1.2 堆內(nèi)存的特點

動態(tài)分配:內(nèi)存在運行時申請、釋放。
可變大?。悍峙涞膬?nèi)存大小可以變化。
基于指針的管理:使用指針訪問和控制內(nèi)存。

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

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

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

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

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

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

1.4 堆內(nèi)存如何工作?

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

主要組成部分:

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

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

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

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

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

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

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

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

$ 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 內(nèi)存分配器:TCMalloc 的繼承與演進

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

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

二、Go 如何管理堆內(nèi)存

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

2.1 案例

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

package main

import (
    "fmt"
    "runtime"
)

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

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

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

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

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

2.2 內(nèi)存分配中的系統(tǒng)調(diào)用

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

執(zhí)行結(jié)果如下:

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     | ←- 調(diào)用 Go 運行時
| - - - - - - - - - - -| 
| Uses syscalls:       | ←- 系統(tǒng)調(diào)用:mmap,munmap
| mmap, munmap         |
| - - - - - - - - - - -| 
| Interacts with OS    | ←- 與操作系統(tǒng)內(nèi)存管理器交互
| Memory Manager       |
+ - - - - - - - - - - -+
                      系統(tǒng)調(diào)用的簡化示例

strace 輸出解釋

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

2.3 內(nèi)存分配過程的各個階段:

+ - - - - - - - - - - -+
| Initialize Slice     | ←- 初始化切片
| [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] |
| - - - - - - - - - - -|
| Set Values           | ←- 設(shè)置值
| [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)內(nèi)存分配和管理的逐步過程。

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

三、 堆內(nèi)存管理:三級結(jié)構(gòu)

Go 的堆內(nèi)存管理是一個精密的三級(或四級)結(jié)構(gòu),理解它就理解了 Go 內(nèi)存管理的核心。

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

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

3.2 第二級:mcentral (Central Cache)

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

3.3 第三級:mheap (Heap Manager)

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

3.4 基礎(chǔ)單元:mspan (Memory Span)

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

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

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

相關(guān)文章

  • 通過案例詳細聊聊Go語言的變量與常量

    通過案例詳細聊聊Go語言的變量與常量

    在任何一門現(xiàn)代的高級語言中,變量和常量都是它非?;A(chǔ)的程序結(jié)構(gòu)的組成部分,下面這篇文章主要給大家介紹了關(guān)于如何通過案例詳細聊聊Go語言的變量與常量的相關(guān)資料,需要的朋友可以參考下
    2023-03-03
  • 一文完全掌握 Go math/rand(源碼解析)

    一文完全掌握 Go math/rand(源碼解析)

    這篇文章主要介紹了一文完全掌握 Go math/rand(源碼解析),本文可以幫助大家快速使用Go Rand.,感興趣的朋友跟隨小編一起看看吧
    2021-04-04
  • Goland支持泛型了(上機實操)

    Goland支持泛型了(上機實操)

    Go的泛型不是還在設(shè)計草圖嗎?最樂觀估計也要2021年8月份。你說Go語言現(xiàn)在都沒開發(fā)好泛型,你支持這個特性有什么用呢?感興趣的朋友跟隨小編一起看看吧
    2020-12-12
  • golang配置管理神器Viper使用教程

    golang配置管理神器Viper使用教程

    這篇文章主要為大家介紹了golang配置管理神器Viper使用教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪
    2022-04-04
  • linux下通過go語言獲得系統(tǒng)進程cpu使用情況的方法

    linux下通過go語言獲得系統(tǒng)進程cpu使用情況的方法

    這篇文章主要介紹了linux下通過go語言獲得系統(tǒng)進程cpu使用情況的方法,實例分析了Go語言使用linux的系統(tǒng)命令ps來分析cpu使用情況的技巧,需要的朋友可以參考下
    2015-03-03
  • Go語言中sync包使用方法教程

    Go語言中sync包使用方法教程

    在Go語言的并發(fā)編程實踐中,性能優(yōu)化總是繞不開的話題,下面這篇文章主要介紹了Go語言中sync包使用方法的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2025-03-03
  • Go語言實現(xiàn)常見限流算法的示例代碼

    Go語言實現(xiàn)常見限流算法的示例代碼

    計數(shù)器、滑動窗口、漏斗算法、令牌桶算法是我們常見的幾個限流算法,本文將依次用Go語言實現(xiàn)這幾個限流算法,感興趣的可以了解一下
    2023-05-05
  • golang環(huán)形隊列實現(xiàn)代碼示例

    golang環(huán)形隊列實現(xiàn)代碼示例

    這篇文章主要介紹了golang環(huán)形隊列實現(xiàn)代碼示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • Go語言定時任務(wù)cron的設(shè)計與使用

    Go語言定時任務(wù)cron的設(shè)計與使用

    這篇文章主要為大家詳細介紹了Go語言中定時任務(wù)cron的設(shè)計與使用,文中的示例代碼講解詳細,對我們深入掌握Go語言有一定的幫助,需要的可以參考下
    2023-11-11
  • Golang基于JWT與Casbin身份驗證授權(quán)實例詳解

    Golang基于JWT與Casbin身份驗證授權(quán)實例詳解

    這篇文章主要為大家介紹了Golang基于JWT與Casbin實現(xiàn)身份驗證授權(quán)實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08

最新評論