Java 中的垃圾回收,經常是由 JVM 幫咱們作好的。雖然這節省了你們不少的學習的成本,提升了項目的執行效率,可是當項目變得愈來愈複雜,用戶量愈來愈大時,仍是須要咱們懂得垃圾回收機制,這樣也能進行更深一步的優化。 git
垃圾回收( Garbage Collection,如下簡稱 GC ),從字面上理解,就是將已經分配出去的,但卻再也不使用的內存回收回來,以便可以再次分配。github
在 JVM 中,垃圾就是指的死亡對象所佔據的堆空間( GC 是發生在堆空間中),那麼咱們若是辨別一個對象是否死亡呢?JVM 使用的是引用計數法
和可達性分析
。安全
引用計數法( Reference Counting),是爲每一個對象添加一個引用計數器,用來統計引用該對象的個數。一旦某個對象的引用計數器爲0,則說明該對象已經死亡,即可以被回收了。多線程
其具體實現爲:學習
若是有一個引用,被賦值爲某一對象,那麼將該對象的引用計數器 +1。優化
若是一個指向某一對象的引用,被賦值爲其餘值,那麼將該對象的引用計數器 -1。線程
也就是說,咱們須要截獲全部的引用更新操做,而且相應地增減目標對象的引用計數器。code
看似很簡單的實現,其實裏面有很多缺陷:cdn
針對第3點,舉個例子特別說明一下:對象
假設對象 a 與 b 相互引用,除此以外沒有其餘引用指向他們。在這種狀況下,a 和 b 實際上已經死了。
但因爲它們的引用計數器皆不爲0(由於相互引用,二者均爲1),在引用計數法的計算中,這兩個對象還活着。所以,這些循環引用對象所佔據的空間將不可回收,從而形成了內存泄露
。
可達性分析( Reachability Analysis ),是目前 JVM 主要採起的斷定對象死亡的方法。實質在於將一系列GC Roots
做爲初始的存活對象合集(live set),而後從該合集出發,探索全部可以被該集合引用到的對象,並將其加入到該集合中,這個過程咱們也稱之爲標記(mark)。最終,未被探索到的對象即是死亡的,是能夠回收的。
那麼什麼是GC Roots
呢?咱們能夠暫時理解爲由堆外指向堆內的引用,通常而言,GC Roots 包括(但不限於)以下幾種:
以前咱們說引用計數法
會有循環引用的問題,可達性分析
就不會了。舉例來講,即使對象 a 和 b 相互引用,只要從 GC Roots 出發沒法到達 a 或者 b,那麼可達性分析便會認爲它們已經死亡。
那可達性分析
有沒有什麼缺點呢?有的,在多線程環境下,其餘線程可能會更新已經分析過的對象中的引用,從而形成誤報(將引用設置爲 null)或者漏報(將引用設置爲未被訪問過的對象)。
誤報並無什麼傷害,JVM 至多損失了部分垃圾回收的機會。漏報則比較麻煩,由於垃圾回收器可能回收事實上仍被引用的對象內存。一旦從原引用訪問已經被回收了的對象,則頗有可能會直接致使 JVM 崩潰。
既然可達性分析
在多線程下有缺點,那 JVM 是如何解決的呢?答案即是 Stop-the-world(如下簡稱JWT
),中止了其餘非垃圾回收線程的工做直到完成垃圾回收。這也就形成了垃圾回收所謂的暫停時間(GC pause)。
那 SWT 是如何實現的呢?當 JVM 收到 SWT 請求後,它會等待全部的線程都到達安全點(Safe Point),才容許請求 SWT 的線程進行獨佔的工做。
那什麼又叫安全點呢?安全點是 JVM 能找到一個穩定的執行狀態,在這個執行狀態下,JVM 的堆棧不會發生變化。
這麼一來,垃圾回收器便可以「安全」地執行可達性分析,全部存活的對象也均可以成功被標記,那麼以後就能夠將死亡的對象進行垃圾回收了。
以上即是發現死亡對象的過程,這也爲以後的垃圾回收進行鋪墊,具體的垃圾回收過程,我會在下一篇文章中講述,敬請期待。
有興趣的話能夠訪問個人博客或者關注個人公衆號、頭條號,說不定會有意外的驚喜。