1. 程式人生 > 實用技巧 >《深入理解Java虛擬機器-第三版-周志明》-垃圾處理器

《深入理解Java虛擬機器-第三版-周志明》-垃圾處理器

垃圾處理器

引言:
我們已經知道jvm管理的執行時資料記憶體大致分為:程式計數器、方法區、虛擬機器棧、本地方方法棧、堆。其中的程式計數器、虛擬機器棧、本地方法棧與執行緒同生同滅,當一個執行之後,虛擬機器棧產生一個棧幀,程式計數器開始記錄位元組碼執行位置,這些都是執行緒私有的。他們的記憶體管理,隨著執行緒的建立而分配,隨著執行緒的銷燬而釋放。
而作為共享區的方法區和堆,是大量例項化候物件,以及記錄類資訊的相關記憶體區域。由於例項話物件只有在執行時才知道建立的物件個數,所以這部分記憶體的分配、回收也是動態的。而方法區類的回收也需要演算法進行判斷。


1.概述

通常垃圾收集被認為是java發展的產物,但其實1960就有人研究過關於垃圾收集問題,大致可以分為三個部分

  • 哪些記憶體需要回收
  • 什麼時候收集
  • 怎麼回收

2.那些記憶體需要回收

哪些記憶體需要回收?當然是不在需要的物件需要回收。那麼,如何判斷一個物件不再需要呢?

2.1引用計數法

簡單說就是在物件中記錄該物件當前被引用的次數,被引用一次就加一,反之減一。引用計數法在java虛擬機器判斷物件存活中很少使用,缺陷之一就是無法解決物件之間迴圈引用的問題。

2.2可達性分析

可達性分析是大多數虛擬機器採用的方法。所有從GC Root出發不能到達的物件,判定為已死物件,需要回收。


可達性分析
很多物件都可以成為GCRoot,比如,同步鎖是有的物件、靜態變數、區域性變數、引數等等。

2.3已死物件就一定會被回收?

被判定為已死的物件也不會立即被回收,真正被回收至少需要兩次標記過程,可達性分析判定不可達時標記一次,隨後進行篩選,如果物件沒有重寫finalize()方法或已經被虛擬機器執行過finalize()方法,那麼該物件才真正宣告死亡。

所以說finalize()方法中,不可達物件依然可以獲救。
如果說,在finalize方法中,物件被其他GC Root物件引用,那麼就不會被清除

2.4方法區能不能回收

雖然說堆中進行垃圾回收的效益很客觀,大概能回收70%~99%的記憶體,而方法去進行垃圾回收往往回收條件苛刻,而且效果不大。但是對於大量使用類載入器的框架,方法區需要得到重視。
方法區的回收物件主要是,常量池中廢棄的常量、不再使用的類。

3.垃圾回收演算法

分代理論:垃圾回收演算法主要有以下三個理論支援,也是大量實驗研究的結果經驗
弱分代理論:大部分物件都是朝生夕死。
強分代理論:熬過垃圾回收的次數越多的物件越難回收。
跨代回收理論:物件關聯大部分只發生在同代之間。

由此,使用分代理論的jvm把堆至少分為新生代和老年代兩個部分。

垃圾回收演算法,主要分為:標記-清除法,標記-整理法,標記-複製法。

3.1標記清除法

顧名思義,就是對不可達的物件/可達物件進行標記,隨後清除/保留。
但是方法由於清理物件的隨機性,會產生或大或小的記憶體間隙,無法獲取到足夠的連續記憶體

3.2標記複製法

1989年,針對新生代朝生夕死的特點,Andrew Appel提出了半代複製的優化,把記憶體劃分為佔80%的Eden和兩塊10%的Survivor。每次收集時,只使用一塊大的和一塊小的,並把存活的物件複製到另一塊小的當中。

針對存活物件佔比多於10%,也就是小的Survivor存不下的情況,Aplel也有響應的逃生門,也就是將多餘的存活物件分配到堆中。

3.3標記整理法

針對老年代大多數存活的特點,標記整理法先對已死物件進行標記,然後任然存活的物件進行移動,移動到一側。然後將邊界之外的記憶體進行清空。可以說標記整理髮和標記清除法的前半部分是一樣的,但是標記真理在後半部分會進行移動。如圖
標記整理法
大量的移動存活物件也是巨大的消耗,而且在移動過程中需要更新其他引用。
但是如果不移動,就會產生記憶體碎片化問題,需要依靠記憶體分配器和訪問器來解決。記憶體訪問是使用者最頻繁的操作,這樣對本就負擔過重記憶體操作加重。
基於以上,不同考慮的收集器會使用不同的演算法。CMS收集器就是基於標記清除演算法的,而Parallel Scavenge收集器就是基於標記整理法的。
另外,作者還提到了一種混合式的方法,就是在碎片化程度低的時候,使用標記清除法,而在碎片化程度不能忍受的時候使用標記整理法。