Go has a new GC Guide, and it's very good, explaining how the garbage collection conceptually works and how it correlates to the two controls you have over it.
Of course the best way is to not allocate so much memory so there's less pressure on the GC...
tldr: set GOMEMLIMIT to 90% of the memory you want to dedicate to it (allow for some extra untracked usage), turn GOGC really high or off and cross your fingers.
How often to run the garbage collector, as a percentage ratio of newly allocated memory to live memory.
For one shot, short lived applications, GC might just be some extra overhead that doesn't have real benefits since you're going to release it all when you exit anyway...
For long running applications, you will probably hit a steady state of memory you actually need and some headroom to grow into before the GC kicks in again.
As a percentage, how much real memory can grow is limited by the base size, so a common trick before Go 1.19 was to use a memory ballast: inflating the block of live memory so there's more room for memory to be allocated before the next GC run.
There's still the problem that you can't easily adjust gc frequency as you approach a limit. So for a short burst of high memory usage just under a limit before OOMKilled, in theory it could run GC more frequently and fly under the limit but it will just continue allocating since its not aware.
The new control in 1.19.
You tell the GC: all this space is yours to use,
and with a high enough GOGC setting (or even
it will use it to the max before triggering a GC run.
To prevent thrashing (running the GC ever more frequently as real usage approaches the limit), the limit is soft (allocations will continue) and trhere's a 50% cpu time limit on GC operations.