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.

one import, then profile production
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
needtool
breakpoints, steppingdlv (Delve), IDE-integrated
CPU / memory hotspotsgo tool pprof
memory leaktwo heap profiles + pprof -base
goroutine leak/debug/pprof/goroutine
latency timeline (closest to JFR)runtime/trace + go tool trace
GC behaviourGODEBUG=gctrace=1
data racesgo test -race
post-mortemGOTRACEBACK=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%.