Section 10 of 11
Debugging & profiling
No JFR, no heap dump. Profiles instead — and they are good.
Add one blank import to a server and you get live CPU, heap, goroutine, mutex and block profiles over HTTP. The thing you genuinely lose versus MAT is the object graph: you cannot ask "who holds a reference to this". You infer ownership from the allocation site instead.
import _ "net/http/pprof" // registers handlers on the default mux
go tool pprof http://host:6060/debug/pprof/heap # memory
go tool pprof http://host:6060/debug/pprof/profile # 30s CPU
go tool pprof http://host:6060/debug/pprof/goroutine # leaked goroutines
go tool pprof -http=:8080 <profile> # flamegraph in the browser
go tool pprof -base old.pprof new.pprof # LEAK HUNT: diff two heaps| need | tool |
|---|---|
| breakpoints, stepping | dlv (Delve), IDE-integrated |
| CPU / memory hotspots | go tool pprof |
| memory leak | two heap profiles + pprof -base |
| goroutine leak | /debug/pprof/goroutine |
| latency timeline (closest to JFR) | runtime/trace + go tool trace |
| GC behaviour | GODEBUG=gctrace=1 |
| data races | go test -race |
| post-mortem | GOTRACEBACK=crash + dlv core |
PGO (profile-guided optimization): commit a real production CPU profile as default.pgo next to your main package and go build picks it up automatically — it inlines hot paths harder and devirtualizes interface calls. It is the JIT trick, done at build time from yesterday’s profile. Typical gain 2-14%.