首頁  >  文章  >  Java  >  Java記憶體機制與GC回收機制的解析(圖文介紹)

Java記憶體機制與GC回收機制的解析(圖文介紹)

不言
不言轉載
2019-02-27 11:21:032878瀏覽

這篇文章帶給大家的內容是關於Java記憶體機制和GC回收機制的解析(圖文介紹),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

Java程式碼執行與編譯的過程

Java記憶體機制與GC回收機制的解析(圖文介紹)

#Java記憶體管理

java記憶體模型分割

Java記憶體機制與GC回收機制的解析(圖文介紹)

物件的存取定位

Object obj = new Object();

Java記憶體機制與GC回收機制的解析(圖文介紹)

java物件創建及初始化

java物件創建之後,就會在堆記憶體擁有自己的一塊區域,接著就是物件的初始化過程。類別成員初始化順序總結:先靜態後普通再建構, 先父類後子類,同級看書寫順序

  1. 先執行父類靜態變數與靜態程式碼區塊,再執行子類別靜態變數與靜態程式碼區塊

  2. 先執行父類別普通變數與程式碼區塊,再執行父類別建構器(static方法)

  3. 先執行子類別普通變數與程式碼區塊,再執行子類別建構器(static方法)

  4. #static方法初始化先於普通方法,靜態初始化只有在必要時刻才進行並且只初始化一次。

注意:子類別的建構方法,不管這個建構方法帶不帶參數,預設的它都會先去尋找父類別的不帶參數的建構方法。如果父類別沒有不帶參數的建構方法,那麼子類別必須用supper關鍵子來呼叫父類別帶參數的建構方法,否則編譯不能通過。

GC回收機制

java中垃圾回收器可以自動回收無用物件佔據的內存,但它只負責釋放java中創建的物件所佔據的所有內存,透過某種創建物件之外的方式為物件分配的記憶體空間則無法被垃圾回收器回收;而且垃圾回收本身也有開銷,GC的優先權比較低,所以如果JVM沒有面臨記憶體耗盡,它是不會去浪費資源進行垃圾回收以恢復記憶體的。最後我們會發現,只要程式沒有瀕臨儲存空間用完那一刻,物件佔用的空間就總也無法釋放。我們可以透過程式碼System.gc()來主動啟動一個垃圾回收器(雖然JVM不會立刻去回收),在釋放new分配記憶體空間之前,將會透過finalize()釋放用其他方法分配的記憶體空間。

哪些記憶體需要回收

java堆、方法區的記憶體

Java記憶體機制與GC回收機制的解析(圖文介紹)

何時回收

  1. 引用計數法

#為物件新增一個引用計數器,每當有一個地方引用它時,計數器加一。反之每當一個引用失效時,計數器減一。當計數器為0時,則表示物件不被引用。舉個例子:
Java記憶體機制與GC回收機制的解析(圖文介紹)
但是,引用計數法無法解決物件之間的循環引用,見下例
Java記憶體機制與GC回收機制的解析(圖文介紹)
  1. 可達性分析

設立若干根物件(GC Root),每個物件都是一個子節點,當一個物件找不到根時,就認為該對像不可達。
Java記憶體機制與GC回收機制的解析(圖文介紹)
沒有一條從根到Object4 和 Object5的路徑,表示這兩個物件到根是不可達的,可以被回收。 java中,可以作為GC Roots的物件包括:java虛擬機器堆疊中引用的物件;方法區中靜態變數所引用的物件;方法區中常數引用的物件;本地方法堆疊中引用的物件。

怎麼回收

  1. 標記-清除演算法

先標記所有需要回收的對象,在標記完成後統一回收所有被標記的對象。
此演算法有兩個問題:1)標記和清除過程效率不高。主要由於垃圾收集器需要從GC Roots根對像中遍歷所有可達的對象,並給這些對象加上一個標記,表明此對像在清除的時候被跳過,然後在清除階段,垃圾收集器會從Java堆中從頭到尾進行遍歷,如果有物件沒有被打上標記,那麼這個物件就會被清除。顯然遍歷的效率是很低的;2)會產生很多不連續的空間碎片,所以可能會導致程序運行過程中需要分配較大的對象的時候,無法找到足夠的內存而不得不提前出發一次垃圾回收。
  1. 複製演算法

將記憶體分成兩塊,每次只使用一塊。當這一塊記憶體滿了,就將還存活的物件複製到另一塊上,並且嚴格按照記憶體位址排列,然後把已使用的那塊記憶體統一回收。
優點是:能夠得到連續的記憶體空間
缺點是:浪費了一半記憶體
現代的JVM並不是依照1:1劃分記憶體空間的,而是將記憶體分成一塊較大的Eden區和兩塊較小的Survivor區,每次使用其中的Eden和一塊Survivor區。回收的時候,將Eden和Survivor中還存活著的物件一次複製到另外一塊Survivor中,最後把Eden和Survivor的空間清理出來。其實這裡還有一個問題:就是如果垃圾回收後,存活的物件需要的空間大於剩餘一塊Survivor的空間怎麼辦?答案是需要依賴其他記憶體進行分配(這裡主要指的是老年代)。
  1. 標記-整理演算法

與標記-清除演算法過程一樣,只不過在標記後不是對未標記的記憶體區域進行清理,二是讓所有的存活物件都往一端移動,然後清理掉邊界外的記憶體
  1. #分代演算法

所謂分代就是根據物件的生命週期把記憶體分成幾塊,這樣就可以根據物件的「年齡」來選擇合適的垃圾回收演算法。在java中,把記憶體中的物件依生命長短分為:1.新生代:生命週期短,例如局部變數;2.老年代:生命週期長的物件;3.永久代:很少會被回收,生命週期長,例如載入的class資訊。
新生代和老年代存放在堆區,永久代存放在方法區。大物件會直接進入老年代,例如很長的字串或很大的數組,大物件對於JVM記憶體分配是個壞消息,因為大物件需要找到連續內存,否則會觸發gc,所以短命的大物件是需要盡量避免的。長期存活的對象進入老年代,對像在新生代每經歷一次minor gc,年齡加1, 默認達到15歲會進入老年代。每次Minor GC時,虛擬機會偵測每次晉升到老年代的平均大小是否大於老年代當前剩餘大小,如果小於,則進行full gc。
新生代使用複製演算法(因為存活的物件較少,而死亡的物件過多,如果使用標記-清除演算法的話,需要遍歷標記,顯然效率較低,而使用複製演算法就可以把存活的較少的物件複製到可用記憶體區域中,這樣效率就較高)進行GC回收,老年代因為存活率高,所以使用標記清除或標記整理演算法回收。

#

以上是Java記憶體機制與GC回收機制的解析(圖文介紹)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除