Go语言如何内存管理

  • A+
所属分类:技术

chatGPT账号

Go语言如何内存管理

Go 内存池

概念介绍
内存池是一种用于管理内存分配的技术,通过预先分配一定数量的内存块,减少频繁的系统调用开销。Go 语言内部也使用了类似的技术来优化内存分配。

Go 内存池实现
Go 语言中的内存池主要通过 runtime 包实现,具体来说,通过 mcentral 和 mspan 结构体来管理内存。

示例代码分析
虽然 Go 语言的内存池实现是内部细节,但可以通过一些示例代码来了解内存分配的过程。

package main

import (
	"fmt"
	"runtime"
	"unsafe"
)

func main() {
	// 分配一个 10 字节的内存块
	data := make([]byte, 10)
	fmt.Printf("Allocated %d bytes\n", len(data))

	// 打印内存分配信息
	runtime.ReadMemStats(&memstats)
	fmt.Printf("Total allocated: %d bytes\n", memstats.Alloc)
	fmt.Printf("Total sys: %d bytes\n", memstats.Sys)
	fmt.Printf("Num alloc: %d\n", memstats.NumAlloc)
	fmt.Printf("Num sys: %d\n", memstats.NumSys)
}

var memstats runtime.MemStats

Go 内存分配器

概念介绍
内存分配器负责管理内存的分配和回收。Go 语言使用了一种称为 分代收集 的策略,通过不同的代来管理不同生命周期的对象。

Go 内存分配器实现
Go 语言的内存分配器主要包括以下几个组件:

  • mcentral: 负责管理内存区域。
  • mspan: 表示一个连续的内存区域。
  • mcache: 线程本地缓存,用于快速分配小对象。

示例代码分析
通过打印内存统计信息,可以观察内存分配的过程。

package main

import (
	"fmt"
	"runtime"
)

func main() {
	// 分配多个对象
	for i := 0; i < 1000; i++ {
		_ = make([]byte, 10)
	}

	// 打印内存统计信息
	runtime.ReadMemStats(&memstats)
	fmt.Printf("Total allocated: %d bytes\n", memstats.Alloc)
	fmt.Printf("Total sys: %d bytes\n", memstats.Sys)
	fmt.Printf("Num alloc: %d\n", memstats.NumAlloc)
	fmt.Printf("Num sys: %d\n", memstats.NumSys)
}

var memstats runtime.MemStats

Go 垃圾收集器

概念介绍
垃圾收集器负责自动回收不再使用的内存。Go 语言使用了一种称为 三色标记复制 的算法来实现垃圾收集。

Go 垃圾收集器实现
Go 语言的垃圾收集器主要包括以下几个阶段:

  • 标记: 标记所有可达的对象。
  • 扫描: 回收未被标记的对象。
  • 复制: 将存活的对象复制到新的内存区域。

示例代码分析
通过设置垃圾收集间隔,可以观察垃圾收集的过程。

package main

import (
	"fmt"
	"runtime"
	"time"
)

func main() {
	// 设置垃圾收集间隔
	runtime.SetGCPercent(-1) // 禁用自动 GC
	runtime.GC()             // 手动触发一次 GC

	// 分配多个对象
	for i := 0; i < 1000; i++ {
		_ = make([]byte, 10)
	}

	// 触发垃圾收集
	runtime.GC()

	// 打印内存统计信息
	runtime.ReadMemStats(&memstats)
	fmt.Printf("Total allocated: %d bytes\n", memstats.Alloc)
	fmt.Printf("Total sys: %d bytes\n", memstats.Sys)
	fmt.Printf("Num alloc: %d\n", memstats.NumAlloc)
	fmt.Printf("Num sys: %d\n", memstats.NumSys)
	fmt.Printf("Num GC: %d\n", memstats.NumGC)

	// 等待一段时间,让 GC 运行
	time.Sleep(2 * time.Second)
}

var memstats runtime.MemStats

详细分析

内存池
内存池通过预先分配内存块来减少系统调用的开销。Go 语言内部通过 mcentral 和 mspan 来管理内存区域。

  • mcentral: 负责管理内存区域。
  • mspan: 表示一个连续的内存区域。

内存分配器
内存分配器负责管理内存的分配和回收。Go 语言使用了 分代收集 策略,通过不同的代来管理不同生命周期的对象。

  • mcentral: 负责管理内存区域。
  • mspan: 表示一个连续的内存区域。
  • mcache: 线程本地缓存,用于快速分配小对象。

垃圾收集器
垃圾收集器负责自动回收不再使用的内存。Go 语言使用了 三色标记复制 算法来实现垃圾收集。

  • 标记: 标记所有可达的对象。
  • 扫描: 回收未被标记的对象。
  • 复制: 将存活的对象复制到新的内存区域。

性能优化

减少内存分配

  • 重用对象: 尽量重用已有的对象,避免频繁分配新对象。
  • 使用缓存: 对于重复使用的对象,可以使用缓存来减少内存分配。

优化垃圾收集

  • 减少对象生命周期: 尽量缩短对象的生命周期,减少垃圾收集的压力。
  • 手动触发 GC: 在适当的时候手动触发垃圾收集,避免过度延迟。

示例代码分析

内存池示例
通过 runtime 包可以观察内存分配和回收的过程。

package main

import (
	"fmt"
	"runtime"
)

func main() {
	// 分配多个对象
	for i := 0; i < 1000; i++ {
		_ = make([]byte, 10)
	}

	// 打印内存统计信息
	runtime.ReadMemStats(&memstats)
	fmt.Printf("Total allocated: %d bytes\n", memstats.Alloc)
	fmt.Printf("Total sys: %d bytes\n", memstats.Sys)
	fmt.Printf("Num alloc: %d\n", memstats.NumAlloc)
	fmt.Printf("Num sys: %d\n", memstats.NumSys)
}

var memstats runtime.MemStats

内存分配器示例
通过 runtime 包可以观察内存分配的过程。

package main

import (
	"fmt"
	"runtime"
)

func main() {
	// 分配多个对象
	for i := 0; i < 1000; i++ {
		_ = make([]byte, 10)
	}

	// 打印内存统计信息
	runtime.ReadMemStats(&memstats)
	fmt.Printf("Total allocated: %d bytes\n", memstats.Alloc)
	fmt.Printf("Total sys: %d bytes\n", memstats.Sys)
	fmt.Printf("Num alloc: %d\n", memstats.NumAlloc)
	fmt.Printf("Num sys: %d\n", memstats.NumSys)
}

var memstats runtime.MemStats

垃圾收集器示例
通过 runtime 包可以观察垃圾收集的过程。

package main

import (
	"fmt"
	"runtime"
	"time"
)

func main() {
	// 设置垃圾收集间隔
	runtime.SetGCPercent(-1) // 禁用自动 GC
	runtime.GC()             // 手动触发一次 GC

	// 分配多个对象
	for i := 0; i < 1000; i++ {
		_ = make([]byte, 10)
	}

	// 触发垃圾收集
	runtime.GC()

	// 打印内存统计信息
	runtime.ReadMemStats(&memstats)
	fmt.Printf("Total allocated: %d bytes\n", memstats.Alloc)
	fmt.Printf("Total sys: %d bytes\n", memstats.Sys)
	fmt.Printf("Num alloc: %d\n", memstats.NumAlloc)
	fmt.Printf("Num sys: %d\n", memstats.NumSys)
	fmt.Printf("Num GC: %d\n", memstats.NumGC)

	// 等待一段时间,让 GC 运行
	time.Sleep(2 * time.Second)
}

var memstats runtime.MemStats

栈内存管理

概念介绍
栈内存主要用于存储函数调用时的局部变量和函数参数。栈内存的特点是先进后出(LIFO),分配和释放速度快,但容量有限。

栈内存分配过程

  • 函数调用:每次函数调用时,都会在栈上分配一块内存来存储局部变量和函数参数。
  • 函数返回:函数执行完毕后,栈上的这块内存会被自动释放。

示例代码分析
通过简单的函数调用来观察栈内存的分配和释放过程。

package main

import (
	"fmt"
	"runtime"
)

func main() {
	// 调用函数
	testStack()
}

func testStack() {
	var a [1000]int // 分配一个较大的数组
	fmt.Println("Array allocated on stack")
	fmt.Printf("Size of array: %d bytes\n", unsafe.Sizeof(a))
	fmt.Printf("Total allocated: %d bytes\n", runtime.MemStats().Alloc)
}

func (m *runtime.MemStats) Alloc() int {
	return int(m.Alloc)
}

指针内存分配详解

概念介绍
指针用于存储内存地址,通过指针可以间接访问内存中的数据。Go 语言中的指针主要有两种类型:栈指针和堆指针。

栈指针
栈指针是指指向栈内存的指针。栈指针通常用于存储局部变量的地址。

堆指针
堆指针是指指向堆内存的指针。堆指针通常用于存储动态分配的数据结构,如数组、切片、结构体等。

示例代码分析
通过示例代码来观察栈指针和堆指针的使用。

package main

import (
	"fmt"
	"unsafe"
	"runtime"
)

func main() {
	// 栈指针示例
	var a int = 10
	fmt.Printf("Stack pointer address: %p\n", &a)
	fmt.Printf("Size of int: %d bytes\n", unsafe.Sizeof(a))

	// 堆指针示例
	b := new(int)
	*b = 20
	fmt.Printf("Heap pointer address: %p\n", b)
	fmt.Printf("Size of int: %d bytes\n", unsafe.Sizeof(*b))

	// 打印内存统计信息
	runtime.ReadMemStats(&memstats)
	fmt.Printf("Total allocated: %d bytes\n", memstats.Alloc)
	fmt.Printf("Total sys: %d bytes\n", memstats.Sys)
	fmt.Printf("Num alloc: %d\n", memstats.NumAlloc)
	fmt.Printf("Num sys: %d\n", memstats.NumSys)
}

var memstats runtime.MemStats

栈内存管理示例

栈内存分配
栈内存主要用于存储函数调用时的局部变量和函数参数。

package main

import (
	"fmt"
	"runtime"
)

func main() {
	// 调用函数
	testStack()
}

func testStack() {
	var a [1000]int // 分配一个较大的数组
	fmt.Println("Array allocated on stack")
	fmt.Printf("Size of array: %d bytes\n", unsafe.Sizeof(a))
	fmt.Printf("Total allocated: %d bytes\n", runtime.MemStats().Alloc)
}

func (m *runtime.MemStats) Alloc() int {
	return int(m.Alloc)
}

代码分析

  • 函数调用:main 函数调用 testStack 函数。
  • 栈内存分配:在 testStack 函数中,分配了一个较大的数组 a。
  • 打印信息:打印数组的大小和当前内存分配情况。
  • 函数返回:testStack 函数执行完毕后,栈上的数组 a 自动释放。

指针内存分配详解

栈指针
栈指针是指向栈内存的指针,通常用于存储局部变量的地址。

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	// 栈指针示例
	var a int = 10
	fmt.Printf("Stack pointer address: %p\n", &a)
	fmt.Printf("Size of int: %d bytes\n", unsafe.Sizeof(a))
}

代码分析

  • 局部变量:声明一个整型变量 a 并赋值为 10。
  • 打印地址:打印变量 a 的地址。
  • 打印大小:打印整型变量的大小。

堆指针

堆指针是指向堆内存的指针,通常用于存储动态分配的数据结构。

package main

import (
	"fmt"
	"unsafe"
	"runtime"
)

func main() {
	// 堆指针示例
	b := new(int)
	*b = 20
	fmt.Printf("Heap pointer address: %p\n", b)
	fmt.Printf("Size of int: %d bytes\n", unsafe.Sizeof(*b))

	// 打印内存统计信息
	runtime.ReadMemStats(&memstats)
	fmt.Printf("Total allocated: %d bytes\n", memstats.Alloc)
	fmt.Printf("Total sys: %d bytes\n", memstats.Sys)
	fmt.Printf("Num alloc: %d\n", memstats.NumAlloc)
	fmt.Printf("Num sys: %d\n", memstats.NumSys)
}

var memstats runtime.MemStats

代码分析

  • 堆内存分配:使用 new 函数分配一个整型变量 b。
  • 打印地址:打印变量 b 的地址。
  • 打印大小:打印整型变量的大小。
  • 打印内存统计信息:打印当前内存分配情况。

综合示例

栈内存和堆内存结合
结合栈内存和堆内存的使用,展示两者的区别。

package main

import (
	"fmt"
	"unsafe"
	"runtime"
)

func main() {
	// 栈指针示例
	var a int = 10
	fmt.Printf("Stack pointer address: %p\n", &a)
	fmt.Printf("Size of int: %d bytes\n", unsafe.Sizeof(a))

	// 堆指针示例
	b := new(int)
	*b = 20
	fmt.Printf("Heap pointer address: %p\n", b)
	fmt.Printf("Size of int: %d bytes\n", unsafe.Sizeof(*b))

	// 打印内存统计信息
	runtime.ReadMemStats(&memstats)
	fmt.Printf("Total allocated: %d bytes\n", memstats.Alloc)
	fmt.Printf("Total sys: %d bytes\n", memstats.Sys)
	fmt.Printf("Num alloc: %d\n", memstats.NumAlloc)
	fmt.Printf("Num sys: %d\n", memstats.NumSys)
}

var memstats runtime.MemStats

免责声明

发文时比特币价格:$71249

当前比特币价格:[crypto coins=”BTC” type=”text” show=”price”]

当前比特币涨幅:[crypto coins=”BTC” type=”text” show=”percent”]

免责声明:

本文不代表路远网立场,且不构成投资建议,请谨慎对待。用户由此造成的损失由用户自行承担,与路远网没有任何关系;

路远网不对网站所发布内容的准确性,真实性等任何方面做任何形式的承诺和保障;

网站内所有涉及到的区块链(衍生)项目,路远网对项目的真实性,准确性等任何方面均不做任何形式的承诺和保障;

网站内所有涉及到的区块链(衍生)项目,路远网不对其构成任何投资建议,用户由此造成的损失由用户自行承担,与路远网没有任何关系;

路远区块链研究院声明:路远区块链研究院内容由路远网发布,部分来源于互联网和行业分析师投稿收录,内容为路远区块链研究院加盟专职分析师独立观点,不代表路远网立场。

本文是全系列中第71 / 309篇:行业技术

  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的电报
  • 这是我的电报扫一扫
  • weinxin
chatGPT账号
路远

发表评论

您必须登录才能发表评论!