這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
自從Go1.5引入了真正的並發GC後, Go1.6進一步進行了最佳化,使得Go在上百G級的堆大小時依然能將STW時間控制在20ms以內:
而Java8的G1收集器,預設參數下在100G以上的heap下,會造成秒級的STW。雖然可以通過-XX:MaxGCPauseMillis
調整,但是是以犧牲大量輸送量為代價。這裡淺析一下Go能做到比G1更短的STW的原因。
輪流掛起協程
JVM的CMS收集器在工作時,大致分為4個階段:
- 初始標記
- 並發標記
- 重新標記
- 並發清除
其中1, 3 是需要STW的階段,CMS的停頓也是由這2個階段引發的。Go1.5中的CMS也分為這些階段,其中1, 3同樣需要STW。那為什麼Go會停頓時間更少呢?原因是,Go的CMS在第3階段並不是掛起所有goroutine,而是輪流掛起。如此一來,3階段就不會造成整個程式的停頓,從而就沒有算入到STW時間之中。
Go觸發GC的時機
Go的gc觸發條件也與JVM的gc有很大區別。JVM通常是堆的使用到達某一閥值,或發生new
操作失敗時gc。而Go則是當從上次gc以來,新建立的對象大小等於上次gc以後存活下來的對象時觸發gc. 這樣,每次gc的壓力就不會像JVM那麼大,STW時間理所當然會短很多,但也犧牲了輸送量。
Go比Java產生更少的記憶體垃圾
Go的對象(即struct類型)是可以分配在棧上的。Go會在編譯時間做靜態逃逸分析(Escape Analysis), 如果發現某個對象並沒有逃出當前範圍,則會將對象分配在棧上而不是堆上,從而減輕了GC壓力。其實JVM也有逃逸分析,但與Go不同的是Java無法在編譯時間做這項工作,分析是在運行時完成的,這樣做一是會佔用更多的CPU時間,二是不可能會把所有未逃逸的對象都最佳化到棧中。
到目前為止,Go的運行效率並沒有因為執行的是本地機器碼而體現出比Java更好的效能優勢,我想這並不怪go, 而是JVM在JIT的協助下其效能已經非常好了。雖然如此,Go的最佳化空間會比JVM更大。相信未來Go的效能會不斷提升,終究會超過JVM。