Golang内存占用与runtime.MemStats统计不符的问题探讨

Golang内存占用与runtime.MemStats统计不符的问题探讨 大家好,

我正在运行一个Go程序,它将数据从MySQL加载到一个映射中,用于长期缓存。数据会在循环中从数据库查询,并每次更新结构体,每次查询少量记录。当以JSON格式写入磁盘时,加载到结构体中的数据量大约为2GB。

加载完成并触发垃圾回收后,我使用runtime.readMemStats()看到了以下内存统计信息:

"Alloc": 11107201856,
  "TotalAlloc": 438869045528,
  "Sys": 54348311616,
  "Lookups": 0,
  "Mallocs": 6589668524,
  "Frees": 6448030009,
  "HeapAlloc": 11107201856,
  "HeapSys": 51584040960,
  "HeapIdle": 38203949056,
  "HeapInuse": 13380091904,
  "HeapReleased": 37270716416,
  "HeapObjects": 141638515,
  "StackInuse": 1703936,
  "StackSys": 1703936,
  "MSpanInuse": 224454240,
  "MSpanSys": 658674288,
  "MCacheInuse": 34800,
  "MCacheSys": 46800,
  "BuckHashSys": 2067482,
  "GCSys": 2037865776,
  "OtherSys": 63912374,
  "NextGC": 22214904160,
  "LastGC": 1688029114272696800,
  "PauseTotalNs": 20106946,

以及Pod消耗的内存:

$k top po  test-pod --containers=true
POD         NAME            CPU(cores)   MEMORY(bytes)   
test-pod    app-container   1m           17367Mi         

结构体定义如下:

type AppCache struct {
	FieldA    map[string]map[string][]map[string]string 
}

结构体中的数据看起来像这样:

{
  "fieldA": {
      "xyz": [
         {"123": "abc"}, {"456": "def"}
      ],
      ...
   }
}

我想知道除了堆分配之外,剩余的内存用在了哪里。有没有办法优化以减少内存占用?


更多关于Golang内存占用与runtime.MemStats统计不符的问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

同一个应用程序的另一个实例占用了25GB的Pod内存

"Alloc": 11107035408,
  "TotalAlloc": 135557988112,
  "Sys": 42268687144,
  "Lookups": 0,
  "Mallocs": 2201319532,
  "Frees": 2059681913,
  "HeapAlloc": 11107035408,
  "HeapSys": 40125399040,
  "HeapIdle": 27178049536,
  "HeapInuse": 12947349504,
  "HeapReleased": 27172528128,
  "HeapObjects": 141637619,
  "StackInuse": 1507328,
  "StackSys": 1507328,
  "MSpanInuse": 216770976,
  "MSpanSys": 490551984,
  "MCacheInuse": 31200,
  "MCacheSys": 31200,
  "BuckHashSys": 1957090,
  "GCSys": 1604467408,
  "OtherSys": 44773094,
  "NextGC": 22214518256,
  "LastGC": 1688032834651951600,
  "PauseTotalNs": 8150878,
$k top po  test-pod --containers=true
POD         NAME            CPU(cores)   MEMORY(bytes)   
test-pod    app-container   1m           25897Mi     

更多关于Golang内存占用与runtime.MemStats统计不符的问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


从内存统计来看,HeapInuse为13.3GB,HeapSys为51.5GB,而Pod显示使用了17.3GB。差异主要来自几个方面:

  1. 堆外内存GCSys(GC元数据)占用了2GB,MSpanSys(内存跨度)占用了658MB,StackSys占用了1.7MB,OtherSys占用了63MB

  2. 内存碎片HeapIdle(空闲堆内存)高达38.2GB,其中HeapReleased(已释放给OS)为37.2GB,但Go运行时可能未及时归还所有内存

  3. 映射结构开销:嵌套映射的每个层级都有额外开销

优化建议:

1. 使用更紧凑的数据结构

type CacheEntry struct {
    Key   string
    Value string
}

type AppCache struct {
    FieldA map[string]map[string][]CacheEntry
}

2. 预分配映射大小减少重哈希

func NewAppCache(estimatedSize int) *AppCache {
    return &AppCache{
        FieldA: make(map[string]map[string][]CacheEntry, estimatedSize),
    }
}

3. 使用sync.Pool重用对象

var entryPool = sync.Pool{
    New: func() interface{} {
        return make([]CacheEntry, 0, 10)
    },
}

func getEntries() []CacheEntry {
    return entryPool.Get().([]CacheEntry)
}

func putEntries(entries []CacheEntry) {
    entries = entries[:0]
    entryPool.Put(entries)
}

4. 定期强制GC并释放内存

func freeMemory() {
    runtime.GC()
    debug.FreeOSMemory()
}

5. 使用更高效的数据结构替代嵌套映射

import "github.com/orcaman/concurrent-map/v2"

type AppCache struct {
    FieldA cmap.ConcurrentMap[string, map[string][]CacheEntry]
}

// 或使用自定义结构
type FlatCache struct {
    data map[string]CacheEntry
    // 添加索引字段
}

type CacheEntry struct {
    Level1 string
    Level2 string
    Pairs  []KeyValue
}

type KeyValue struct {
    Key   string
    Value string
}

6. 监控具体内存分配

import (
    "runtime"
    "runtime/debug"
)

func printMemoryStats() {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    
    fmt.Printf("HeapAlloc = %v MiB", bToMb(m.HeapAlloc))
    fmt.Printf("\tHeapSys = %v MiB", bToMb(m.HeapSys))
    fmt.Printf("\tHeapInuse = %v MiB", bToMb(m.HeapInuse))
    fmt.Printf("\tHeapIdle = %v MiB\n", bToMb(m.HeapIdle))
    
    // 查看对象数量
    fmt.Printf("HeapObjects = %v\n", m.HeapObjects)
}

func bToMb(b uint64) uint64 {
    return b / 1024 / 1024
}

7. 使用pprof分析内存分布

import _ "net/http/pprof"

// 在main函数中
go func() {
    http.ListenAndServe("localhost:6060", nil)
}()

// 然后访问 http://localhost:6060/debug/pprof/heap?debug=1

主要内存差异来自Go运行时的内部管理和数据结构开销。通过优化数据结构和内存管理策略,可以显著减少实际内存占用。

回到顶部