垃圾回收機制(Garbage Collection,GC)是 java語言的特性穴翩,有了 GC犬第,不用再像 c/c++ 那樣麻煩地考慮內存的分配和釋放。其實芒帕,垃圾回收機制的歷史遠遠要比java的歷史久遠歉嗓,因為在1960年誕生的Lisp是第一門應用垃圾回收機制的語言。雖然有了垃圾回收機制幫我們完成垃圾回收背蟆,但是我們還是得了解 GC 的運行機制鉴分,以便于在開發(fā)中排查內存泄露和內存溢出等問題带膀,通過優(yōu)化 GC 來提供應用的性能冠场。
即是對過內存了解的不多,您可能也聽過內存通常分為棧內存和堆內存本砰。棧和堆是兩種不同的數據結構碴裙,我們將物理的內存抽象為這兩種數據結構,便于管理內存(如何訪問內存以及內存地址間的關系)。垃圾回收機制主要作用于 java 的堆(Heap)內存舔株,堆內存也用于存放 java 對象實例莺琳,所以很多時候我們也把 java堆成為GC堆。
在程序的運行時载慈,需要在堆內存中的對象越來越多惭等,堆內存中沒有足夠的空間存放新對象。就得想辦法來回收已不再用的對象办铡,來釋放堆內存的空間辞做,那么垃圾回收機制在進行垃圾回收前,就得判斷哪些實例已不再使用寡具,哪些實例還要繼續(xù)使用秤茅,我們用可達算法來判斷對象是否存活。
在主流商用語言(如Java童叠、C#)的主流實現中, 都是通過可達性分析算法來判定對象是否存活的: 通過一系列的稱為 GC Roots 的對象作為起點, 靜態(tài)屬性和在棧內存中方法中的本地變量所引用的對象框喳。然后向下搜索,搜索所走過的路徑稱為引用鏈/Reference Chain, 當一個對象到 GC Roots 沒有任何引用鏈相連時, 即該對象不可達, 也就說明此對象是不可用的, 如下圖: Object5厦坛、6五垮、7 雖然互有關聯, 但它們到GC Roots是不可達的, 因此也會被判定為可回收的對象。
那么我們具體如何劃分堆內存的呢杜秸?現在通常做法放仗,將內存劃分一定區(qū)域,我們通常按對象存活的時間對堆內存中的對象進行劃分撬碟,這就是分代算法匙监。也是流行的 GC 算法。我們這里需要了解一個規(guī)律小作,就是大多數的對象都是來也匆匆去也匆匆亭姥,他們的存活是短暫的。所以我們新看 GC 是如何處理年輕代的內存的顾稀。我們將年輕代的內存再次細分為三部分 Eden 达罗、 survivor to? 和 survivor from 三個部分,首先我們還需了解我 GC 時通常是先對內存進行標記静秆,標記出哪些內存是不可達的粮揉,也就是他們將被 GC 掉,GC 掉那些不可達(沒有被引用的)對象后抚笔,在堆內存中會留下許多空位扶认,我們還需要處理這些空位,類似 windows 的清理磁盤碎片殊橙。我們也需要清理掉這些空位辐宾,讓內存連續(xù)狱从,這個過程大家可以理解為壓縮內存。
當 Eden 區(qū)域新生的對象過多是叠纹,就會觸發(fā) Minor GC 來清理 Eden 區(qū)的對象
我們將標記為可達對象移動到 survivor 區(qū)域季研,綠色的表示存活的對象,然后灰色對象表示不可達的對象誉察。
我們清除掉那些不再被引用的對象(灰色表示)与涡,這些綠色對象經歷一次 minor GC 的浩劫存活下來了。
隨著 Eden 的新的對象有不斷涌現持偏,我們又需要進行 minor GC 這一次我們依舊以同樣的方法來處理 Eden 中對象驼卖。
1. 將 survivor 1 中的可達對象移動到 survivor 2 中,剩下的都是不可達的對象
將 survivor 2 中的的內存標記為 2鸿秆,然后在下一次 minorGC 將為 1 的存活的對象移動到 survior 2 中酌畜,這樣好處就是我們通過來回移動對象,可以無需考慮連續(xù)性谬莹,這樣我們通過犧牲空間來換取效率,無需考慮如何壓縮內存桩了。
這樣反復幾次附帽,如果還存活的對象反復次數超過閥值就會晉升到老年代