Golang为什么占用那么多的虚拟内存原理解析

 更新时间:2024年01月16日 11:46:45   作者:磊丰 Go语言圈  
这篇文章主要介绍了Golang为什么占用那么多的虚拟内存原理解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

正文

Go占用虚拟内存的大小可能受到多种因素的影响,包括运行时的内存分配、goroutine的数量、程序逻辑等。虚拟内存的大小并不等同于实际使用的物理内存大小,因为虚拟内存包括未分配的内存空间。

以下是一些可能导致Go程序占用较多虚拟内存的原因,以及如何使用 pprof 包进行内存分析:

以下是一些常见的原因:

  • 内存分配问题:Go有自己的内存分配策略,使用了一种称为"mmap"的技术,这可能导致程序占用更多的虚拟内存。这样的设计能够更好地支持并发和垃圾回收。

  • GC(垃圾回收)机制:Go的垃圾回收机制可能会导致虚拟内存的增长。垃圾回收过程中,可能会有一些未释放的内存。

  • COW(写时复制)机制:Go在进行内存分配时,采用了写时复制的机制,这也可能导致虚拟内存的增长

1. 内存分配问题

Go中的内存分配是由运行时管理的,而不是手动控制。如果程序中存在频繁的内存分配和释放,可能导致虚拟内存的增加。

package main
import (
    "fmt"
    "time"
)
func allocateMemory() {
    for i := 0; i < 100000; i++ {
        _ = make([]byte, 1024)
    }
}
func main() {
    allocateMemory()
    fmt.Println("Memory allocated.")
    time.Sleep(time.Hour) // 保持程序运行以便分析
}

2 GC(垃圾回收)机制

垃圾回收通过标记-清除算法和并发处理来实现。在垃圾回收过程中,可能存在一些未释放的内存,但这是正常的行为,Go会在需要的时候进行垃圾回收。

package main
import (
    "fmt"
    "runtime"
    "time"
)
// Object 是一个简单的对象结构
type Object struct {
    data []byte
}
func main() {
    // 设置每秒触发一次垃圾回收
    go func() {
        for {
            runtime.GC()
            time.Sleep(time.Second)
        }
    }()
    // 创建对象并让它们变得不可达
    for i := 0; i < 10; i++ {
        createObjects()
        time.Sleep(500 * time.Millisecond)
    }
}
func createObjects() {
    // 创建一些对象并让它们变得不可达
    for i := 0; i < 10000; i++ {
        obj := createObject()
        _ = obj
    }
}
func createObject() *Object {
    // 创建一个对象
    obj := &Object{
        data: make([]byte, 1024),
    }
    return obj
}

3. COW(写时复制)机制

在Go中,写时复制(Copy-On-Write)机制通常指的是当一个值被复制时,实际上只有在需要修改其中一个副本时才进行真正的复制,这样可以节省内存。

在Go的切片(slice)和映射(map)中,由于它们是引用类型,采用了写时复制的策略。下面是一个简单的例子:

package main
import (
    "fmt"
)
func main() {
    // 创建一个切片
    slice1 := []int{1, 2, 3, 4, 5}
    // 创建另一个切片,实际上并没有复制底层数组
    slice2 := slice1
    // 修改第一个切片,此时会触发写时复制
    slice1[0] = 99
    // 输出两个切片的值
    fmt.Println("Slice 1:", slice1) // 输出 [99 2 3 4 5]
    fmt.Println("Slice 2:", slice2) // 输出 [99 2 3 4 5]
}

例子中,当 slice1 修改后,Go 会检测到 slice2 也引用了相同的底层数组,因此会触发写时复制,将底层数组复制一份,使得两个切片的底层数组不再共享。

这种写时复制的机制有助于减少内存占用,因为只有在有修改的时候才会进行复制。然而,需要注意的是,如果在并发环境下同时修改两个切片,可能会导致意外的结果,因为它们的底层数组不再共享。因此,在并发编程中,需要采取适当的同步措施。

4. Goroutine 数量

每个goroutine都有自己的栈空间,如果程序启动了大量的goroutine,可能会导致虚拟内存的增加。

package main
import (
    "fmt"
    "runtime"
    "time"
)
func createGoroutines() {
    for i := 0; i < 100000; i++ {
        go func() {
            time.Sleep(time.Hour)
        }()
    }
}
func main() {
    createGoroutines()
    fmt.Println("Goroutines created.")
    time.Sleep(time.Hour) // 保持程序运行以便分析
}

使用 `pprof` 进行内存分析

1 导入 net/http/pprof 包并启动一个HTTP服务器:

import (
    _ "net/http/pprof"
    "net/http"
)
func main() {
    go func() {
        http.ListenAndServe("localhost:6060", nil)
    }()
    // Your program logic here
}

2 启动程序,并访问 http://localhost:6060/debug/pprof/ 进行分析。

例如,你可以访问 http://localhost:6060/debug/pprof/heap 查看堆内存的分配情况。

go run your_program.go

3 使用 go tool pprof 进行命令行分析:

package main
import (
    "net/http"
    _ "net/http/pprof"
    "time"
)
func main() {
    go func() {
        // 启动 pprof 服务器
        http.ListenAndServe("localhost:6060", nil)
    }()
    // 模拟一个可能导致虚拟内存增长的操作
    for {
        allocateMemory()
        time.Sleep(1 * time.Second)
    }
}
func allocateMemory() {
    // 模拟内存分配
    data := make([]byte, 1<<20) // 分配1MB内存
    _ = data
}

在上述代码中,我们在一个goroutine中启动了pprof服务器,然后在allocateMemory函数中模拟了一个可能导致虚拟内存增长的操作。

运行程序并进行分析

1 运行程序:

go run your-program.go

2 打开浏览器,访问 http://localhost:6060/debug/pprof/,可以看到各种 pprof 的分析页面。

3 点击 heap 页面,可以查看内存使用情况。

4 如果想使用命令行工具进行分析,可以使用 go tool pprof

go tool pprof http://localhost:6060/debug/pprof/heap

然后可以使用不同的命令进行分析,比如 toplist 等。

通过分析 pprof 数据,你可以更详细地了解程序的内存使用情况,找到可能导致虚拟内存增长的原因。

以上就是Golang为什么占用那么多的虚拟内存原理解析的详细内容,更多关于Golang虚拟内存占用的资料请关注脚本之家其它相关文章!

相关文章

  • go原子级内存操作实现

    go原子级内存操作实现

    原子级内存操作是在多线程并发执行时,能够确保某个内存操作是不可中断的操作,本文主要介绍了go原子级内存操作实现,具有一定的参考价值,感兴趣的可以了解一下
    2024-02-02
  • go实现fping功能

    go实现fping功能

    这篇文章主要介绍了go实现fping功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • go语言文件正则表达式搜索功能示例

    go语言文件正则表达式搜索功能示例

    这篇文章主要介绍了go语言文件正则表达式搜索功能,涉及Go语言文件目录的遍历及正则操作相关技巧,需要的朋友可以参考下
    2017-01-01
  • go语言中sort包的实现方法与应用详解

    go语言中sort包的实现方法与应用详解

    golang中也实现了排序算法的包sort包,所以下面这篇文章主要给大家介绍了关于go语言中sort包的实现方法与应用的相关资料,文中通过示例代码介绍的非常详细,需要的朋友们可以参考借鉴,下面随着小编来一起学习学习吧。
    2017-11-11
  • Go语言中函数的使用方法详解

    Go语言中函数的使用方法详解

    这篇文章主要为大家详细介绍了Go语言中函数的使用方法的相关资料,文中的示例代码讲解详细,对我们学习Go语言有一定的帮助,感兴趣的可以了解一下
    2023-04-04
  • Go语言中的闭包详解

    Go语言中的闭包详解

    本文详细讲解了Go语言中的闭包,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-07-07
  • golang context接口类型方法介绍

    golang context接口类型方法介绍

    这篇文章主要为大家介绍了golang context接口类型方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • GO语言协程创建使用并通过channel解决资源竞争

    GO语言协程创建使用并通过channel解决资源竞争

    这篇文章主要为大家介绍了GO语言协程创建使用并通过channel解决资源竞争,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2022-04-04
  • 使用Go语言构建一个最小可用的TCP交互程序

    使用Go语言构建一个最小可用的TCP交互程序

    这篇文章主要为大家详细介绍了如何使用Go语言构建一个最小可用的TCP交互程序,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2026-03-03
  • Go语言转化php数组的示例代码

    Go语言转化php数组的示例代码

    这篇文章主要为大家详细介绍了Go语言如何实现转化php数组的相关知识,文中的示例代码讲解详细,对我们深入学习GO语言有一定的帮助,需要的可以参考下
    2023-11-11

最新评论