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语言原子操作及互斥锁的区别,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12
  • golang操作mongodb的方法

    golang操作mongodb的方法

    这篇文章主要介绍了golang操作mongodb的方法,涉及Go语言操作mongodb的连接、读取及显示的方法,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • Fedora14 Linux系统安装Golang开发环境笔记

    Fedora14 Linux系统安装Golang开发环境笔记

    这篇文章主要介绍了Fedora14 Linux系统安装Golang开发环境笔记,本文讲解了2种安装方法,需要的朋友可以参考下
    2014-10-10
  • 用go gin server来做文件上传服务

    用go gin server来做文件上传服务

    今天小编就为大家分享一篇关于用go gin server来做文件上传服务,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-04-04
  • golang mysql的连接池的具体使用

    golang mysql的连接池的具体使用

    本文主要介绍了golang mysql的连接池的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • Golang中的占位符详解

    Golang中的占位符详解

    这篇文章主要给大家详细总结了Golang中的占位符用法,文章通过代码示例介绍的非常详细,对我们学习Golang占位符有一定的帮助,需要的朋友可以参考下
    2023-07-07
  • Go语言中日期包(time包)的具体使用

    Go语言中日期包(time包)的具体使用

    本文主要介绍了Go语言中日期包的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05
  • go语言优雅地处理error工具及技巧详解

    go语言优雅地处理error工具及技巧详解

    这篇文章主要为大家介绍了go语言优雅地处理error工具及技巧详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • GO语言数组和切片实例详解

    GO语言数组和切片实例详解

    这篇文章主要介绍了GO语言数组和切片的用法,以实例形式较为详细的分析了GO语言中数组与切片的创建及使用技巧,是深入学习GO语言的基础,需要的朋友可以参考下
    2014-12-12
  • GoLang RabbitMQ实现六种工作模式示例

    GoLang RabbitMQ实现六种工作模式示例

    这篇文章主要介绍了GoLang RabbitMQ实现六种工作模式,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-12-12

最新评论