最近的一個golang項(xiàng)目上生產(chǎn)后戒幔,發(fā)現(xiàn)RES內(nèi)存占用一直在輕微上漲:三天時間從一開始的40M,上漲到了60M左右派诬。之前對golang的內(nèi)存管理和垃圾回收機(jī)制一直不是很清晰,于是花些時間去把這個問題搞清楚吝羞。
1) VSZ,RSS,TTY,STAT, VIRT,RES,SHR,DATA的含義
我們需要關(guān)注的主要是RES內(nèi)存
o: VIRT -- Virtual Image (kb)
The total amount of virtual memory used by the task. It
includes all code, data and shared libraries plus pages that
have been swapped out and pages that have been mapped but not
used.
p: SWAP -- Swapped size (kb)
Memory that is not resident but is present in a task. This is
memory that has been swapped out but could include additional
non-resident memory. This column is calculated by subtracting
physical memory from virtual memory.
q: RES -- Resident size (kb)
The non-swapped physical memory a task has used.
r: CODE -- Code size (kb)
The amount of virtual memory devoted to executable code, also
known as the 'text resident set' size or TRS.
s: DATA -- Data+Stack size (kb)
The amount of virtual memory devoted to other than executable
code, also known as the 'data resident set' size or DRS.
t: SHR -- Shared Mem size (kb)
The amount of shared memory used by a task. It simply reflects
memory that could be potentially shared with other processes.
VIRT:virtual memory usage 虛擬內(nèi)存
1、進(jìn)程“需要的”虛擬內(nèi)存大小内颗,包括進(jìn)程使用的庫钧排、代碼、數(shù)據(jù)等等均澳。
2恨溜、假如進(jìn)程申請100m的內(nèi)存,但實(shí)際只使用了10m找前,那么它會增長100m糟袁,而不是實(shí)際的使用量
SHR:shared memory 共享內(nèi)存
1、除了自身進(jìn)程的共享內(nèi)存躺盛,也包括其他進(jìn)程的共享內(nèi)存
2、雖然進(jìn)程只使用了幾個共享庫的函數(shù),但它包含了整個共享庫的大小
3牙勘、計算某個進(jìn)程所占的物理內(nèi)存大小公式:RES – SHR
4骇钦、swap out后,它將會降下來
RES:resident memory usage 常駐內(nèi)存
1界斜、進(jìn)程當(dāng)前使用的內(nèi)存大小仿耽,但不包括swap out
2、包含其他進(jìn)程的共享
3各薇、如果申請100m的內(nèi)存项贺,實(shí)際使用10m,它只增長10m峭判,與VIRT相反
4开缎、關(guān)于庫占用內(nèi)存的情況,它只統(tǒng)計加載的庫文件所占內(nèi)存大小
DATA
1朝抖、數(shù)據(jù)占用的內(nèi)存啥箭。如果top沒有顯示,按f鍵可以顯示出來治宣。
2急侥、真正的該程序要求的數(shù)據(jù)空間,是真正在運(yùn)行中要使用的侮邀。
2) golang監(jiān)控內(nèi)存的方式
1 利用GCVIS進(jìn)行可視化監(jiān)控
原理可參考:http://holys.im/2016/07/01/monitor-golang-gc/
這是最簡單的方式坏怪,好處是不用修改程序代碼。
1)安裝
go get https://github.com/davecheney/gcvis 或者git clone https://github.com/davecheney/gcvis 到本地自行安裝
2)啟動監(jiān)控(要運(yùn)行的文件是/path/to/binary)
有兩種方式
1绊茧、 直接運(yùn)行
./gcvis /path/to/binary
2铝宵、 管道重定向方式(standard error)
GODEBUG=gctrace=1 /path/to/binary |& ./gcvis
我這里運(yùn)行的是
env GOMAXPROCS=4 ./gcvis -o=false -p 12345 -i 10.107.101.1 /path/to/binary
可訪問頁面http://10.107.101.1:12345 獲取可視化結(jié)果。
2 利用pprof進(jìn)行監(jiān)控
具體可參考:https://studygolang.com/articles/9940
go中有pprof包來做代碼的性能監(jiān)控主要涉及兩個pkg:
#web服務(wù)器:
import (
"net/http"
_ "net/http/pprof"
)
#一般應(yīng)用程序(實(shí)際應(yīng)用無web交互)
import (
"net/http"
_ "runtime/pprof"
)
其實(shí)net/http/pprof中只是使用runtime/pprof包來進(jìn)行封裝了一下,并在http端口上暴露出來
1)修改代碼
需要對原來的程序代碼進(jìn)行修改
web 服務(wù)器
如果你的go程序是用http包啟動的web服務(wù)器鹏秋,可以選擇net/http/pprof尊蚁。
只需要引入包_"net/http/pprof",然后就可以在瀏覽器中使用 http://localhost:port/debug/pprof/
直接看到當(dāng)前web服務(wù)的狀態(tài)侣夷,包括CPU占用情況和內(nèi)存使用情況等横朋。
服務(wù)進(jìn)程
如果你的go程序不是web服務(wù)器,而是一個服務(wù)進(jìn)程百拓,也可以選擇使用net/http/pprof包琴锭,同樣引入包net/http/pprof,然后在開啟另外一個goroutine來開啟端口監(jiān)聽衙传。
go func() {
http.ListenAndServe("localhost:6060", nil)
}()
應(yīng)用程序
如果你的go程序只是一個應(yīng)用程序决帖,比如計算fabonacci數(shù)列,那么你就不能使用net/http/pprof包了蓖捶,你就需要使用到runtime/pprof地回。
具體做法就是用到pprof.StartCPUProfile和pprof.StopCPUProfile。
比如下面的例子:
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
func main() {
flag.Parse()
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
運(yùn)行程序的時候加一個--cpuprofile參數(shù)腺阳,比如 fabonacci --cpuprofile=fabonacci.prof
這樣程序運(yùn)行的時候的cpu信息就會記錄到XXX.prof中了落君。
2)查看監(jiān)控數(shù)據(jù)
兩種方式:
1 WEB上直接查看,瀏覽器直接訪問:http://XXX:6060/debug/pprof
2 通過 go tool pprof 查看
具體的數(shù)據(jù)字段含義參考
https://blog.csdn.net/m0_38132420/article/details/71699815
https://studygolang.com/articles/9940
3) FreeOSMemory()
折騰和很久亭引,發(fā)現(xiàn)兩種方式監(jiān)控到的內(nèi)存都比較穩(wěn)定绎速。但是TOP命令查看RES還是一直在增長。
查了很久的資料:
https://groups.google.com/forum/#!topic/Golang-Nuts/kuS4kLCwkbE
https://stackoverflow.com/questions/37382600/cannot-free-memory-once-occupied-by-bytes-buffer
https://golang.org/pkg/runtime/debug/#FreeOSMemory
最后發(fā)現(xiàn)焙蚓,golang的內(nèi)存即使被gc回收了纹冤,也不會立刻歸還給OS的,除非你手動調(diào)用FreeOSMemory()
這也是RES內(nèi)存一起比預(yù)期中高的原因
Some things to clear. Go is a garbage collected language, which means that memory allocated and used by variables is automatically freed by the garbage collector when those variables become unreachable (if you have another pointer to the variable, that still counts as "reachable").
Freed memory does not mean it is returned to the OS. Freed memory means the memory can be reclaimed, reused for another variable if there is a need. So from the operating system you won't see memory decreasing right away just because some variable became unreachable and the garbage collector detected this and freed memory used by it.
The Go runtime will however return memory to the OS if it is not used for some time (which is usually around 5 minutes). If the memory usage increases during this period (and optionally shrinks again), the memory will most likely not be returned to the OS
FreeOSMemory forces a garbage collection followed by an attempt to return as much memory to the operating system as possible. (Even if this is not called, the runtime gradually returns memory to the operating system in a background task.)