Golang中为什么GC如此频繁

Golang中为什么GC如此频繁 环境: go1.21.0 GOGC: 500 GOMEMLIMIT: 532MB

需要注意的是,GOMEMLIMIT 基于以下模式动态变化:

  1. 默认从 532MB 开始。
  2. 每 100 毫秒,它会检查条件 if float64(r.HeapInuse)*(float64(100+gogc) / 100) > float64(debug.SetMemoryLimit(-1))。当此条件为真时,它假定上一次垃圾回收是由 GOMEMLIMIT 而非 GOGC 触发的。在这种情况下,它将 GOMEMLIMIT 设置为 math.MaxInt64。
  3. 1 分钟后,它将 GOMEMLIMIT 恢复为 532MB。

我在 TiDB 上长时间运行一个工作负载,gctrace 日志如下所示。

gc 445 [@474](/user/474).461s 0%: 0.090+14+0.028 ms clock, 1.4+5.2/57/80+0.44 ms cpu, 279->285->162 MB, 307 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 446 [@474](/user/474).816s 0%: 0.12+13+0.23 ms clock, 2.0+0.48/50/115+3.8 ms cpu, 291->294->158 MB, 311 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 447 [@475](/user/475).127s 0%: 0.10+12+0.073 ms clock, 1.6+1.1/49/97+1.1 ms cpu, 291->295->159 MB, 312 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 448 [@475](/user/475).413s 0%: 0.11+11+0.093 ms clock, 1.7+0.45/45/102+1.4 ms cpu, 281->284->158 MB, 301 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 449 [@475](/user/475).836s 0%: 0.10+11+0.065 ms clock, 1.6+0.36/44/94+1.0 ms cpu, 300->304->160 MB, 317 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 450 [@476](/user/476).177s 0%: 0.35+18+0.005 ms clock, 5.6+1.7/72/132+0.083 ms cpu, 290->292->157 MB, 305 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 451 [@476](/user/476).654s 0%: 0.095+11+0.020 ms clock, 1.5+3.3/45/87+0.33 ms cpu, 303->306->159 MB, 318 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 452 [@477](/user/477).039s 0%: 0.10+12+0.017 ms clock, 1.6+4.8/49/103+0.27 ms cpu, 300->304->160 MB, 315 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 453 [@477](/user/477).325s 0%: 0.099+10+0.093 ms clock, 1.5+2.8/42/93+1.4 ms cpu, 287->289->158 MB, 302 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 454 [@477](/user/477).604s 0%: 0.27+13+0.005 ms clock, 4.3+2.0/49/87+0.082 ms cpu, 300->303->159 MB, 315 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 455 [@477](/user/477).941s 0%: 0.083+12+0.041 ms clock, 1.3+2.6/48/92+0.67 ms cpu, 302->306->160 MB, 317 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 456 [@478](/user/478).348s 0%: 0.10+11+0.020 ms clock, 1.6+0.53/46/119+0.33 ms cpu, 291->292->156 MB, 305 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 457 [@478](/user/478).741s 0%: 0.13+12+0.035 ms clock, 2.1+1.5/49/112+0.57 ms cpu, 307->309->157 MB, 321 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 458 [@479](/user/479).148s 0%: 0.16+15+0.087 ms clock, 2.6+6.5/60/93+1.4 ms cpu, 301->307->161 MB, 317 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 459 [@479](/user/479).444s 0%: 0.070+11+0.080 ms clock, 1.1+0.40/45/100+1.2 ms cpu, 285->290->160 MB, 304 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 460 [@479](/user/479).763s 0%: 0.097+11+0.015 ms clock, 1.5+0.34/45/100+0.24 ms cpu, 297->298->157 MB, 313 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 461 [@480](/user/480).208s 0%: 0.60+12+0.049 ms clock, 9.7+1.1/49/85+0.78 ms cpu, 286->293->161 MB, 304 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 462 [@480](/user/480).564s 0%: 0.094+17+0.11 ms clock, 1.5+5.7/70/100+1.7 ms cpu, 307->314->161 MB, 329 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 463 [@482](/user/482).727s 0%: 0.14+17+0.054 ms clock, 2.2+0.49/70/118+0.87 ms cpu, 957->962->160 MB, 975 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 464 [@484](/user/484).536s 0%: 0.37+11+0.019 ms clock, 6.0+0.98/46/112+0.30 ms cpu, 953->955->157 MB, 971 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 465 [@486](/user/486).275s 0%: 0.15+12+0.034 ms clock, 2.4+0.75/50/100+0.55 ms cpu, 937->941->159 MB, 955 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 466 [@488](/user/488).012s 0%: 0.10+17+0.022 ms clock, 1.6+2.9/67/125+0.35 ms cpu, 949->961->237 MB, 967 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 467 [@490](/user/490).975s 0%: 1.0+64+2.7 ms clock, 16+2.7/229/361+44 ms cpu, 1251->1261->306 MB, 1434 MB goal, 0 MB stacks, 1 MB globals, 16 P (forced)
gc 468 [@495](/user/495).188s 0%: 1.0+93+0.44 ms clock, 16+46/371/380+7.1 ms cpu, 1800->1839->535 MB, 1849 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 469 [@502](/user/502).153s 0%: 0.27+23+0.052 ms clock, 4.3+11/90/149+0.84 ms cpu, 3125->3131->177 MB, 3219 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 470 [@504](/user/504).633s 0%: 0.21+22+0.085 ms clock, 3.4+5.7/88/131+1.3 ms cpu, 1053->1062->174 MB, 1074 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 471 [@507](/user/507).698s 0%: 0.52+21+0.021 ms clock, 8.4+6.0/84/119+0.34 ms cpu, 1031->1034->169 MB, 1055 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 472 [@510](/user/510).185s 0%: 0.19+17+0.026 ms clock, 3.1+7.5/68/133+0.42 ms cpu, 1002->1007->171 MB, 1026 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 473 [@510](/user/510).545s 0%: 0.16+105+0.011 ms clock, 2.6+708/420/456+0.18 ms cpu, 309->309->166 MB, 171 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 474 [@510](/user/510).687s 0%: 0.11+17+0.037 ms clock, 1.9+5.9/70/108+0.59 ms cpu, 166->178->169 MB, 166 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 475 [@510](/user/510).722s 0%: 0.11+14+0.015 ms clock, 1.8+19/57/94+0.24 ms cpu, 169->176->166 MB, 169 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 476 [@510](/user/510).755s 0%: 0.048+14+0.035 ms clock, 0.77+1.6/55/89+0.56 ms cpu, 166->176->168 MB, 338 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 477 [@512](/user/512).199s 0%: 0.41+13+0.045 ms clock, 6.5+0.80/55/113+0.72 ms cpu, 539->547->170 MB, 569 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 478 [@513](/user/513).748s 0%: 0.11+17+0.071 ms clock, 1.8+3.1/71/116+1.1 ms cpu, 541->548->170 MB, 573 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 479 [@515](/user/515).103s 0%: 0.11+18+0.091 ms clock, 1.7+0.50/73/116+1.4 ms cpu, 544->553->172 MB, 576 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 480 [@516](/user/516).518s 0%: 0.22+15+0.035 ms clock, 3.6+2.7/62/129+0.57 ms cpu, 553->557->167 MB, 579 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 481 [@517](/user/517).774s 0%: 0.12+21+0.064 ms clock, 2.0+0.30/83/178+1.0 ms cpu, 555->558->165 MB, 582 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 482 [@519](/user/519).141s 0%: 0.14+13+0.013 ms clock, 2.3+4.7/53/100+0.21 ms cpu, 551->556->168 MB, 578 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 483 [@520](/user/520).313s 0%: 0.13+14+0.040 ms clock, 2.1+1.9/57/120+0.64 ms cpu, 552->555->166 MB, 576 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 484 [@521](/user/521).324s 0%: 0.23+16+0.12 ms clock, 3.7+3.9/64/121+1.9 ms cpu, 557->562->169 MB, 581 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 485 [@522](/user/522).193s 0%: 0.10+15+0.050 ms clock, 1.6+8.2/59/103+0.80 ms cpu, 568->575->171 MB, 583 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 486 [@523](/user/523).714s 0%: 0.14+14+0.022 ms clock, 2.2+1.0/56/120+0.35 ms cpu, 564->568->168 MB, 585 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 487 [@525](/user/525).076s 0%: 0.28+14+0.078 ms clock, 4.6+1.8/56/120+1.2 ms cpu, 560->564->168 MB, 582 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 488 [@526](/user/526).446s 0%: 0.15+12+0.019 ms clock, 2.5+5.0/50/94+0.31 ms cpu, 562->566->168 MB, 584 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 489 [@527](/user/527).440s 0%: 0.081+16+0.044 ms clock, 1.3+1.7/63/119+0.71 ms cpu, 560->564->169 MB, 581 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 490 [@528](/user/528).306s 0%: 0.18+18+0.039 ms clock, 2.9+4.8/70/93+0.62 ms cpu, 558->567->175 MB, 581 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 491 [@529](/user/529).193s 0%: 0.37+14+0.069 ms clock, 6.0+6.9/56/113+1.1 ms cpu, 557->561->168 MB, 581 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 492 [@530](/user/530).171s 0%: 0.23+15+0.041 ms clock, 3.6+0.52/60/132+0.66 ms cpu, 555->558->168 MB, 578 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 493 [@531](/user/531).227s 0%: 0.14+14+0.041 ms clock, 2.3+3.2/57/106+0.66 ms cpu, 561->566->170 MB, 585 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 494 [@532](/user/532).117s 0%: 0.16+16+0.14 ms clock, 2.5+6.7/64/105+2.3 ms cpu, 559->564->171 MB, 582 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 495 [@533](/user/533).018s 0%: 0.19+19+0.089 ms clock, 3.0+7.9/77/122+1.4 ms cpu, 558->567->174 MB, 583 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 496 [@533](/user/533).837s 0%: 0.18+17+0.021 ms clock, 2.9+3.8/68/123+0.35 ms cpu, 555->561->171 MB, 580 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 497 [@534](/user/534).708s 0%: 0.13+18+0.084 ms clock, 2.1+5.6/70/105+1.3 ms cpu, 554->561->172 MB, 579 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 498 [@535](/user/535).504s 0%: 0.15+18+0.026 ms clock, 2.4+4.1/73/109+0.41 ms cpu, 557->565->173 MB, 581 MB goal, 0 MB stacks, 1 MB globals, 16 P
gc 499 [@536](/user/536).262s 0%: 0.11+20+0.

更多关于Golang中为什么GC如此频繁的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中为什么GC如此频繁的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


从gctrace日志可以看出,GC确实非常频繁,尤其是在474-477秒期间出现了连续的GC。这主要是由GOMEMLIMIT的动态调整机制和GOGC=500的组合导致的。

关键问题分析

1. GOMEMLIMIT动态调整机制

根据你描述的逻辑,当HeapInuse * (100+GOGC)/100 > MemoryLimit时,GOMEMLIMIT会被设为MaxInt64,1分钟后恢复。这会导致内存使用模式剧烈变化。

2. GOGC=500设置过高

GOGC=500意味着堆内存增长到上次GC后的5倍时才触发GC。但在GOMEMLIMIT机制下,这个设置会与内存限制产生冲突。

具体日志分析

// 正常阶段:内存稳定在~160MB左右
gc 445 @474.461s: 279->285->162 MB, 307 MB goal
gc 446 @474.816s: 291->294->158 MB, 311 MB goal

// 第463次GC:内存突然增长到~960MB
gc 463 @482.727s: 957->962->160 MB, 975 MB goal

// 第466次GC:内存继续增长
gc 466 @488.012s: 949->961->237 MB, 967 MB goal

// 第467次GC:强制GC,内存达到1.2GB
gc 467 @490.975s: 1251->1261->306 MB, 1434 MB goal (forced)

// 第468次GC:内存达到1.8GB
gc 468 @495.188s: 1800->1839->535 MB, 1849 MB goal

// 第469次GC:内存飙升至3.1GB后回收
gc 469 @502.153s: 3125->3131->177 MB, 3219 MB goal

// 第473-476次GC:连续快速GC(间隔仅0.1-0.3秒)
gc 473 @510.545s: 309->309->166 MB, 171 MB goal
gc 474 @510.687s: 166->178->169 MB, 166 MB goal  // 间隔0.14秒
gc 475 @510.722s: 169->176->166 MB, 169 MB goal  // 间隔0.035秒
gc 476 @510.755s: 166->176->168 MB, 338 MB goal  // 间隔0.033秒

问题根源

  1. GOMEMLIMIT动态切换导致内存使用不稳定:当GOMEMLIMIT设为MaxInt64时,内存可以无限制增长,直到触发GOGC=500的条件(增长5倍)。
  2. 内存大幅波动引发频繁GC:内存从160MB增长到3.1GB,然后急剧下降,这种波动导致GC频繁触发。
  3. GOGC与GOMEMLIMIT的冲突:GOGC=500期望内存增长5倍才GC,但GOMEMLIMIT=532MB限制了最大内存,两者目标不一致。

解决方案示例

方案1:固定GOMEMLIMIT,调整GOGC

// 在程序启动时设置
func main() {
    // 固定内存限制,避免动态调整
    debug.SetMemoryLimit(2 * 1024 * 1024 * 1024) // 2GB固定限制
    
    // 降低GOGC值,减少内存波动
    os.Setenv("GOGC", "100")  // 内存增长1倍就触发GC
    
    // ... 程序逻辑
}

方案2:完全禁用GOMEMLIMIT动态调整

// 如果你需要保持GOGC=500的行为
func main() {
    // 设置足够高的固定限制,让GOGC主导GC触发
    debug.SetMemoryLimit(math.MaxInt64)
    
    // 或者完全禁用GOMEMLIMIT
    debug.SetMemoryLimit(0)
    
    // 依赖GOGC=500来控制GC频率
    os.Setenv("GOGC", "500")
    
    // ... 程序逻辑
}

方案3:监控和调优内存使用模式

// 添加内存监控,了解实际使用模式
import (
    "runtime"
    "time"
)

func monitorMemory() {
    ticker := time.NewTicker(100 * time.Millisecond)
    defer ticker.Stop()
    
    for range ticker.C {
        var m runtime.MemStats
        runtime.ReadMemStats(&m)
        
        // 记录关键指标
        log.Printf("HeapInuse: %vMB, HeapAlloc: %vMB, NextGC: %vMB", 
            m.HeapInuse/1024/1024,
            m.HeapAlloc/1024/1024,
            m.NextGC/1024/1024)
    }
}

当前配置的问题

你的当前配置(GOGC=500 + 动态GOMEMLIMIT)导致:

  1. 内存使用在160MB和3.1GB之间剧烈波动
  2. GOMEMLIMIT动态切换时产生大量连续GC
  3. GC频率从几秒一次变为毫秒级连续触发

建议选择GOGC或GOMEMLIMIT其中一种作为主要GC触发机制,避免两者冲突。对于TiDB这类内存敏感的应用,建议使用固定的GOMEMLIMIT配合适中的GOGC值(如100-200)。

回到顶部