自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

五分鐘搞懂 Golang 堆內(nèi)存

系統(tǒng)
本文主要解釋了堆內(nèi)存的概念,介紹了 Linux 堆內(nèi)存的工作原理,以及 Golang 如何管理堆內(nèi)存。

你想過(guò)為什么堆內(nèi)存被稱為 "堆" 嗎?想象一下雜亂堆放的對(duì)象,與此類似,在計(jì)算機(jī)中,堆內(nèi)存是動(dòng)態(tài)分配和釋放內(nèi)存的空間,通常會(huì)導(dǎo)致內(nèi)存塊的無(wú)序排列。我們可以利用這種相似性和無(wú)序排列來(lái)理解堆內(nèi)存,并探討堆內(nèi)存的概念及其在計(jì)算中的意義。

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

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

1. 進(jìn)程的內(nèi)存布局

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

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

                     進(jìn)程內(nèi)存布局

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

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

通過(guò)簡(jiǎn)單介紹,可以看到內(nèi)存是如何有效組織,以滿足運(yùn)行進(jìn)程的靜態(tài)和動(dòng)態(tài)需求。堆的作用對(duì)于動(dòng)態(tài)內(nèi)存分配尤為重要,從而允許程序靈活高效的管理內(nèi)存。

2. 堆內(nèi)存的特點(diǎn)

  • 動(dòng)態(tài)分配:內(nèi)存在運(yùn)行時(shí)申請(qǐng)、釋放。可變大?。悍峙涞膬?nèi)存大小可以變化?;谥羔樀墓芾恚菏褂弥羔樤L問(wèn)和控制內(nèi)存。

下圖演示了如何通過(guò)將堆內(nèi)存劃分為多個(gè)空閑塊和已分配塊來(lái)動(dòng)態(tà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.          | ←- 空閑塊
+ - - - - - - - - - - -+

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

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

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

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

主要組成部分:

  • 堆內(nèi)存段:進(jìn)程內(nèi)存中保留用于動(dòng)態(tài)分配的部分
  • mmap:調(diào)整數(shù)據(jù)段末尾以增加或減少堆大小的系統(tǒng)調(diào)用
  • malloc 和 free:C 庫(kù)提供的函數(shù),用于分配和釋放堆上的內(nèi)存
  • 內(nèi)存管理器:C 庫(kù)的一個(gè)組件,用于管理堆,跟蹤已分配和已釋放的內(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)抽象掉了底層細(xì)節(jié),在內(nèi)部與操作系統(tǒng)的內(nèi)存管理機(jī)制進(jìn)行了交互。

1. 實(shí)例

我們通過(guò)一個(gè)簡(jiǎn)單的 Go 程序來(lái)理解,該程序?yàn)檎麛?shù)片段分配內(nèi)存、初始化數(shù)值并打印。

package main

import (
    "fmt"
    "runtime"
)

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

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

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

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

為了了解 Go 如何與 Linux 內(nèi)存管理庫(kù)交互,可以使用 strace(我最喜歡的工具)來(lái)跟蹤 Go 程序進(jìn)行的系統(tǒng)調(diào)用。

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

$ go build -o memory_allocation main.go
$ strace -f -e trace=mmap,munmap ./memory_allocation
mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff94da0000
mmap(NULL, 131072, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff94d80000
mmap(NULL, 1048576, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff94c80000
mmap(NULL, 8388608, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff94400000
mmap(NULL, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff90400000
mmap(NULL, 536870912, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff70400000
mmap(NULL, 536870912, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff50400000
mmap(0x4000000000, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4000000000
mmap(NULL, 33554432, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff4e400000
mmap(NULL, 68624, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff94c6f000
mmap(0x4000000000, 4194304, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x4000000000
mmap(0xffff94d80000, 131072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff94d80000
mmap(0xffff94c80000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff94c80000
mmap(0xffff94402000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff94402000
mmap(0xffff90410000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff90410000
mmap(0xffff70480000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff70480000
mmap(0xffff50480000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff50480000
mmap(NULL, 1048576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff4e300000
mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff94c5f000
mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff94c4f000
strace: Process 1141999 attached
strace: Process 1142000 attached
strace: Process 1142001 attached
[pid 1141998] --- SIGURG {si_signo=SIGURG, si_code=SI_TKILL, si_pid=1141998, si_uid=0} ---
[pid 1142000] mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff94c0f000
strace: Process 1142002 attached
5 5 5 5 5 5 5 5 5 5
[pid 1142001] mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff4e2c0000
[pid 1141998] --- SIGURG {si_signo=SIGURG, si_code=SI_TKILL, si_pid=1141998, si_uid=0} ---
[pid 1142000] mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff4e2b0000
[pid 1141998] mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff4e270000
[pid 1142002] +++ exited with 0 +++
[pid 1142001] +++ exited with 0 +++
[pid 1142000] +++ exited with 0 +++
[pid 1141999] +++ exited with 0 +++
+++ exited with 0 +++
+ - - - - - - - - - - -+
| Go Program           | ←- Go 程序
| - - - - - - - - - - -| 
| Calls Go Runtime     | ←- 調(diào)用 Go 運(yùn)行時(shí)
| - - - - - - - - - - -| 
| Uses syscalls:       | ←- 系統(tǒng)調(diào)用:mmap,munmap
| mmap, munmap         |
| - - - - - - - - - - -| 
| Interacts with OS    | ←- 與操作系統(tǒng)內(nèi)存管理器交互
| Memory Manager       |
+ - - - - - - - - - - -+
                      系統(tǒng)調(diào)用的簡(jiǎn)化示例

3. strace 輸出解釋

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

4. 內(nèi)存分配過(guò)程的各個(gè)階段

+ - - - - - - - - - - -+
| 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             | ←- 強(qiáng)制垃圾回收
| - - - - - - - - - - -|

上圖說(shuō)明了 Go 動(dòng)態(tài)內(nèi)存分配和管理的逐步過(guò)程。

(1) 初始化切片:

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

切片(動(dòng)態(tài)數(shù)組)的初始狀態(tài)為 10 個(gè)元素,全部設(shè)置為 0。這一步展示了 Go 如何為切片分配內(nèi)存。

(2) 設(shè)置值:

[5, 5, 5, 5, 5, 5, 5, 5, 5, 5]

然后,在切片的每個(gè)元素中填入值 5。這一步演示了如何初始化和使用分配的內(nèi)存。

(3) 打印值:

5 5 5 5 5 5 5 5 5 5

打印切片的值,確認(rèn)內(nèi)存分配和初始化成功。這一步驗(yàn)證程序是否正確訪問(wèn)和使用了分配的內(nèi)存。

(4) 強(qiáng)制 GC(垃圾回收)

手動(dòng)觸發(fā)垃圾回收器,釋放不再使用的內(nèi)存。這一步強(qiáng)調(diào) Go 的自動(dòng)內(nèi)存管理和清理過(guò)程,確保了資源的有效利用。

四、總結(jié)

堆內(nèi)存是現(xiàn)代計(jì)算的重要方面,它實(shí)現(xiàn)了動(dòng)態(tài)內(nèi)存分配,使程序能在運(yùn)行時(shí)有效管理內(nèi)存。這種靈活性對(duì)于處理鏈表、樹(shù)、圖等動(dòng)態(tài)數(shù)據(jù)結(jié)構(gòu)至關(guān)重要,因?yàn)檫@些結(jié)構(gòu)無(wú)法在編譯時(shí)預(yù)先確定。了解堆內(nèi)存對(duì)于開(kāi)發(fā)人員編寫高效、穩(wěn)健的應(yīng)用至關(guān)重要,可確保有效使用內(nèi)存,并在不再需要時(shí)釋放資源。

通過(guò)探討堆內(nèi)存在 Linux 中的工作原理以及 Go 如何管理動(dòng)態(tài)內(nèi)存分配,希望本文能為你提供有關(guān)內(nèi)存管理內(nèi)部運(yùn)作的寶貴見(jiàn)解。掌握這些概念不僅有助于編寫更好的代碼,還有助于調(diào)試和優(yōu)化應(yīng)用程序。

責(zé)任編輯:趙寧寧 來(lái)源: DeepNoMind
相關(guān)推薦

2025-01-20 08:50:00

2024-12-11 07:00:00

面向?qū)ο?/a>代碼

2025-03-13 06:22:59

2024-12-04 16:12:31

2019-08-09 10:33:36

開(kāi)發(fā)技能代碼

2021-05-28 07:38:20

內(nèi)存溢出場(chǎng)景

2025-03-18 09:20:00

Go語(yǔ)言Golang

2023-12-06 08:48:36

Kubernetes組件

2023-09-18 15:49:40

Ingress云原生Kubernetes

2024-01-29 00:20:00

GolangGo代碼

2022-05-23 09:10:00

分布式工具算法

2024-04-29 07:57:46

分布式流控算法

2021-06-18 07:34:12

Kafka中間件微服務(wù)

2017-03-30 19:28:26

HBase分布式數(shù)據(jù)

2018-09-27 13:56:14

內(nèi)網(wǎng)外網(wǎng)通信

2023-10-06 20:21:28

Python鏈表

2009-11-16 10:53:30

Oracle Hint

2020-06-16 08:47:53

磁盤

2025-04-16 08:20:00

LinuxELF文件

2021-06-07 09:51:22

原型模式序列化
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)