← 中文资源

简体中文版本

堆分析(Heap Profiling)

堆分析通过采样或追踪分配显示哪些代码路径保留内存,把模糊的泄漏报告定位到具体分配点。

阅读英文原文

概念概述

堆分析(Heap Profiling)(Heap Profiling)指的是:堆分析通过采样或追踪分配显示哪些代码路径保留内存,把模糊的泄漏报告定位到具体分配点。

从更严格的定义看,Heap profiling is the measurement of memory allocation and retention, typically by sampling allocation stacks or by capturing heap snapshots, to attribute growth to code locations. 该概念在工程实践中的价值,在于它能帮助团队用统一的语言描述一个具体的质量属性或设计约束,从而在评审与协作中减少歧义。

为什么需要它

It is used to diagnose leaks, excessive allocation pressure, and garbage collection pauses in long-running processes. 它与 Centralised Log Aggregation、The USE Method for Resource Analysis、Symptom-Based Alerting 等概念经常一起出现,用于解决相近的工程问题。理解它的适用场景,有助于在多种备选方案之间做出有依据的取舍,而不是凭直觉选择技术方案。

常见误解

  • heap profiling requires stopping the process
  • a growing heap always means a leak rather than deferred collection

需要避免的反模式

  • reading allocation profiles in production without accounting for sampling bias
  • treating a post-collection snapshot as the same as a live one

实践要点

  1. Sampling profilers record a fraction of allocations and extrapolate, so they add low overhead but their totals are estimates rather than exact counts.
  2. Comparing snapshots before and after a workload distinguishes memory that is retained from memory that was merely allocated transiently.
  3. Retention matters more than allocation volume: a hot path that allocates heavily but frees promptly is a throughput concern, not a leak.

参考来源

https://pprof-docs.example/

代码示例

package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/pprof"
	"runtime"
	"time"
)

func busyWork() {
	total := 0
	for i := 0; i < 2_000_000; i++ {
		total += i % 7
	}
	_ = total
}

func setupProfiler(addr string) *http.Server {
	mux := http.NewServeMux()
	mux.HandleFunc("/debug/pprof/", pprof.Index)
	mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
	mux.HandleFunc("/debug/pprof/heap", pprof.Handler("heap").ServeHTTP)
	mux.HandleFunc("/debug/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP)

	server := &http.Server{Addr: addr, Handler: mux}
	go func() {
		log.Printf("pprof listening on %s", addr)
		if err := server.ListenAndServe(); err != http.ErrServerClosed {
			log.Printf("pprof server error: %v", err)
		}
	}()
	return server
}

func main() {
	prof := setupProfiler("127.0.0.1:6060") // 仅内网可达,不暴露公网
	defer prof.Close()

	runtime.SetMutexProfileFraction(5)
	runtime.SetBlockProfileRate(200)

	for i := 0; i < 3; i++ {
		busyWork()
		time.Sleep(100 * time.Millisecond)
	}

	// 使用方式:
	// go tool pprof http://127.0.0.1:6060/debug/pprof/profile?seconds=10
	// go tool pprof http://127.0.0.1:6060/debug/pprof/heap
	fmt.Println("profile samples collected")
}