Golang中堆大小变化过大的问题探讨

Golang中堆大小变化过大的问题探讨

$  sudo strace -qq  -f -e madvise,brk -p  1234
[pid  2323] brk(NULL)                   = 0x1092000
[pid  2323] brk(NULL)                   = 0x1092000
[pid  2323] brk(0x10b3000)              = 0x10b3000
[pid  2323] brk(NULL)                   = 0x10b3000
[pid  2323] brk(NULL)                   = 0x10b3000
[pid  2323] brk(NULL)                   = 0x56538b58b000
[pid  2323] brk(NULL)                   = 0x56538b58b000
[pid  2323] brk(0x56538b5ac000)         = 0x56538b5ac000
[pid  2323] brk(NULL)                   = 0x56538b5ac000
[pid 12568] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2323, si_uid=0, si_status=0, si_utime=0, si_stime=0} ---
[pid  2367] brk(NULL)                   = 0xff2000
[pid  2367] brk(NULL)                   = 0xff2000
[pid  2367] brk(0x1013000)              = 0x1013000
[pid  2367] brk(NULL)                   = 0x1013000
[pid  2367] brk(NULL)                   = 0x1013000
[pid  2367] brk(NULL)                   = 0x55d9c9efb000
[pid  2367] brk(NULL)                   = 0x55d9c9efb000
[pid  2367] brk(0x55d9c9f1c000)         = 0x55d9c9f1c000
[pid  2367] brk(NULL)                   = 0x55d9c9f1c000
[pid 12536] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2367, si_uid=0, si_status=0, si_utime=0, si_stime=0} ---
[pid  2414] brk(NULL)                   = 0x128b000
[pid  2414] brk(NULL)                   = 0x128b000
[pid  2414] brk(0x12ac000)              = 0x12ac000
[pid  2414] brk(NULL)                   = 0x12ac000
[pid  2414] brk(NULL)                   = 0x12ac000
[pid  2414] brk(NULL)                   = 0x564bb31a3000
[pid  2414] brk(NULL)                   = 0x564bb31a3000
[pid  2414] brk(0x564bb31c4000)         = 0x564bb31c4000
[pid  2414] brk(NULL)                   = 0x564bb31c4000
[pid 12530] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2414, si_uid=0, si_status=0, si_utime=0, si_stime=0} --

brk 系统调用变化太大,而且数值似乎与 maps 文件不匹配。

sudo cat /proc/1234/maps | grep heap
01a73000-01a94000 rw-p 00000000 00:00 0                                  [heap]

更多关于Golang中堆大小变化过大的问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中堆大小变化过大的问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


从你提供的strace输出和maps文件内容来看,这展示了Go语言运行时内存管理的典型行为。Go使用了自己设计的内存分配器,而不是完全依赖操作系统的brk/sbrk机制,这导致了观察到的现象。

问题分析

Go运行时维护了多个内存段(spans),包括:

  • 使用mmap分配的较大内存块
  • 传统的堆段(通过brk扩展)
  • 用于goroutine栈的内存区域

在你的strace输出中,可以看到:

  • brk(NULL)调用查询当前堆顶地址
  • brk(addr)调用扩展堆段
  • 地址从较低值(如0x1092000)跳转到较高值(如0x56538b58b000

Go内存管理机制

Go运行时使用混合内存分配策略:

package main

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

func main() {
    // 展示Go内存分配行为
    var stats runtime.MemStats
    
    // 分配一些内存来观察行为
    data := make([]byte, 1024*1024) // 1MB
    for i := range data {
        data[i] = byte(i % 256)
    }
    
    runtime.ReadMemStats(&stats)
    fmt.Printf("HeapAlloc: %d bytes\n", stats.HeapAlloc)
    fmt.Printf("HeapSys: %d bytes\n", stats.HeapSys)
    fmt.Printf("HeapIdle: %d bytes\n", stats.HeapIdle)
    fmt.Printf("HeapInuse: %d bytes\n", stats.HeapInuse)
    
    // 强制GC来观察内存回收
    runtime.GC()
    time.Sleep(100 * time.Millisecond)
    
    runtime.ReadMemStats(&stats)
    fmt.Printf("After GC - HeapAlloc: %d bytes\n", stats.HeapAlloc)
}

地址不匹配的原因

maps文件显示的[heap]段(01a73000-01a94000)只是传统意义上的堆段,而Go运行时实际上使用了多个内存段:

package main

import (
    "fmt"
    "os"
    "runtime"
)

func showMemorySegments() {
    var stats runtime.MemStats
    runtime.ReadMemStats(&stats)
    
    fmt.Printf("Go runtime memory segments:\n")
    fmt.Printf("Mapped memory (HeapSys): %d bytes\n", stats.HeapSys)
    fmt.Printf("Allocated heap (HeapAlloc): %d bytes\n", stats.HeapAlloc)
    fmt.Printf("Stack memory (StackSys): %d bytes\n", stats.StackSys)
    
    // 分配大块内存观察地址变化
    largeBlock := make([]byte, 10*1024*1024) // 10MB
    fmt.Printf("Large block address: %p\n", &largeBlock[0])
    
    // 这个地址可能不在传统的[heap]段中
    // 因为Go可能使用mmap分配大对象
}

内存分配示例

package main

import (
    "runtime"
    "sync"
    "time"
)

func memoryIntensiveWork() {
    var wg sync.WaitGroup
    
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            
            // 每个goroutine分配不同大小的内存
            size := 1024 * (id + 1)
            data := make([]byte, size)
            
            for j := range data {
                data[j] = byte((i + j) % 256)
            }
            
            time.Sleep(10 * time.Millisecond)
        }(i)
    }
    
    wg.Wait()
    
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    println("Total allocated:", m.HeapAlloc, "bytes")
}

结论

你观察到的brk调用变化大且与maps文件不匹配是Go运行时的正常行为。Go的内存分配器:

  1. 对小对象使用线程本地缓存
  2. 对大对象直接使用mmap
  3. 维护多个内存段而非单一堆段
  4. 动态调整堆大小以适应工作负载

这种行为是设计上的优化,旨在提高内存分配性能和减少锁竞争。

回到顶部