One of the perks of Golang is that we don't have to manage memory ourselves. There is a garbage collector which uses a mark-and-sweep algorithm to figure out which objects in memory are not used and marks those for collection.
The pace of GC cycles depends on the variables, which are:
GOGC, which determines at what next size of the total heap garbage collection should happen. If it is set to 100, which means it's 100% of the current live heap at the end of the GC cycle, the next target heap size is 2x the current size (it is approximate due to GC roots, which are negligible compared to the live heap).
Target heap memory = Live heap + (Live heap + GC roots) * GOGC / 100
There is a new variable, GOMEMLIMIT, which was introduced in Golang 1.19. The need for this new configuration variable was if the live heap keeps on increasing, then the next target heap size might be more than the available memory on the host, which can trigger OOM.
And in general, if we are setting GOMEMLIMIT, we should set it to a maximum of 90 to 95% of the total memory in the system, because some memory must be reserved for other system level tasks. Another thing that can happen is that if we set GOMEMLIMIT too low and the live heap size is more than this limit, then the system will try to bring the total heap used down, which won't be possible, resulting in thrashing. https://tip.golang.org/doc/gc-guide#:~:text=This%20situation%2C%20where,the%20heap%20spike

Note: GC variables should be configured in a way to reduce the number of CPU cycles that are spent on garbage collection while still keeping the average heap size over time manageable. Both are inversely proportional.
A brief overview of how the goroutine scheduler works, because we will be discussing how GC stops processors P for collection. In the scheduler, there are no ofprocessors can be set by the GOMAXPROCS env. Each time a goroutine is to be processed, an OS thread, which is denoted by M, is attached to the P, for executing G the goroutine. https://agrim123.com/posts/goroutines-mpg.html.
When these goroutines are not working, they can be parked in the local queue of either P or in the global queue. There are two other types of interrupts where the goroutine execution is stopped:
M is blocked with the goroutine G, and then it is detached from the processor. The processor can spawn another OS thread for executing another goroutine. In this case, both the M and G are stopped.network poller to return the status so that gorountine, can be picked up by some processor, and it G can start reading from or writing to the file descriptor.When the next heap size target is hit, GC performs three steps:
Stop the world. At the start of the GC cycle, all Ps and Ms are stopped. For stopping them, all the goroutines have to return to a safe point, i.e. a function call. If there's a goroutine which has a lot of internal computation, it will delay the GC execution, due to delay in reaching safe point. Once the "stop the world" happens at the start of the GC cycle, marking starts.

Marking. In this stage, about 25% of the available processors are taken to help with the collection process. During this, all other P are available to perform other computations, and then GC mark all the objects that have no references to them.

If it's taking a lot of time, then some of the other processors will also be marked as mark assists, to help in figuring out which references are valid and helping in finishing the marking process faster.

Termination. GC performs "stop the world" and collect all the objects that are not live.
There is also a step called sweeping, which marks the newly deallocated space as available. The cost of sweeping is very negligible compared to the other steps. As a result, it's not talked about from the optimization or performance POV.
To enable GC logs for checking the GC state, use GODEBUG=gctrace=1 go run main.go
gc 1405 @6.068s 11%: 0.058+1.2+0.083 ms clock, 0.70+2.5/1.5/0+0.99 ms cpu, 7->11->6 MB, 10 MB goal, 12 P
// General
gc 1404 : The 1404 GC run since the program started
@6.068s : Six seconds since the program started
11% : Eleven percent of the available CPU so far has been spent in GC
// Wall-Clock
0.058ms : STW : Mark Start - Write Barrier on
1.2ms : Concurrent : Marking
0.083ms : STW : Mark Termination - Write Barrier off and clean up
// CPU Time
0.70ms : STW : Mark Start
2.5ms : Concurrent : Mark - Assist Time (GC performed in line with allocation)
1.5ms : Concurrent : Mark - Background GC time
0ms : Concurrent : Mark - Idle GC time
0.99ms : STW : Mark Term
// Memory
7MB : Heap memory in-use before the Marking started
11MB : Heap memory in-use after the Marking finished
6MB : Heap memory marked as live after the Marking finished
10MB : Heap target to trigger current gc cycle
// Threads
12P : Number of logical processors or threads used to run Goroutines