Golang 가지고 놀다 프로그램이 메모리를 먹고 놔주지를 않는 것 같아서 한번 알아본 것들 정리.




GOGC는 중요한 특징이 두 가지 있는데..


1. Go는 힙 사용량이 지난번 힙 사용량의 2배가 될 때마다 GC를 진행한다.


그래서 점유한 메모리가 현재 실제로 사용중인 메모리의 2배까지 늘어날 수 있는데, 이 비율은 GOGC 값으로 커스텀 가능.


2. 사용중이 아님을 확인한 메모리라 하더라도 OS에 바로 반환하지 않는다.


운영체제에서 자원을 할당받고 반환하는 과정은 비용이 많이 들기에, Go는 나중에 일어날 힙 할당에 대비하여 필요없어진 힙 공간이라 하더라도 OS에 돌려주지 않음.


그래서 작업관리자 같은 OS 모니터링 프로그램을 볼 때 Go 프로세스가 필요도 없는데 메모리를 잔뜩 먹고 돌려주지를 않는 것을 볼 수 있다.


한 5분간 새 힙 메모리 할당이 없다면, 먹었던 메모리를 슬슬 OS에 돌려주기 시작하는 것을 볼 수 있음.




자, 그럼 어떻게 메모리 퍼먹는 것을 고칠 수 있느냐?


기본적으로 사용하지 않는 변수를 nil로 초기화하면 됨.


함수 반환이나 채널 삽입 전에 필요없는 대용량 값들을 nil로 맞추면 대부분은 합리적인 메모리 사용량을 보여줌.


아니면 runtime.GC() debug.FreeOSMemory() 로 강제로 GC를 돌릴 수 있다.


runtime.GC()는 가비지 콜렉터를 실행시켜서 사용중이 아닌 힙 메모리를 해제하지만, 그걸 OS에 반환하지는 않음.


반면 debug.FreeOSMemory()는GC를 하고 사용중이 아닌 메모리를 바로 OS에 반환함.




이 차이를 잘 보여주는 코드 4개를 만들어 봤음.



참고로 OS에 바로 반환하지 않는 것은 DLL 등으로 컴파일 할 때도 마찬가지임.


그러니까 Go로 만든 DLL을 Python ctypes 등으로 가져와서 돌릴 때,


CGo malloc으로 할당한 메모리는 CGo free 쓰면 바로 해제되지만, Go 내부적으로 쓴 메모리는 바로 반환되지 않고 5분쯤 있어야 OS에 반환됨.




내가 처음에 Go DLL 파이썬이랑 연동하려다가 메모리를 계속 처먹고 OS에 반환을 안해서 어디서 누수가 나는지 의심했었는데,


그런 거 없다. 둘다 GC 잘 동작한다. DLL에서 뭔가를 호출해서 Go 코드를 안 돌려도 새 작업 없이 놔두면 알아서 OS에 반환하더라.


Golang 작고 가벼운데다 쓰면 쓸수록 설계를 잘 했다는 점이 느껴저서 만족스러움