1. 程式人生 > 程式設計 >Java分代垃圾回收策略原理詳解

Java分代垃圾回收策略原理詳解

一、為什麼要分代

分代的垃圾回收策略,是基於這樣一個事實:不同的物件的生命週期是不一樣的。因此,不同生命週期的物件可以採取不同的收集方式,以便提高回收效率。

在Java程式執行的過程中,會產生大量的物件,其中有些物件是與業務資訊相關,比如Http請求中的Session物件、執行緒、Socket連線,這類物件跟業務直接掛鉤,因此生命週期比較長。但是還有一些物件,主要是程式執行過程中生成的臨時變數,這些物件生命週期會比較短,比如:String物件,由於其不變類的特性,系統會產生大量的這些物件,有些物件甚至只用一次即可回收。

試想,在不進行物件存活時間區分的情況下,每次垃圾回收都是對整個堆空間進行回收,花費時間相對會長,同時,因為每次回收都需要遍歷所有存活物件,但實際上,對於生命週期長的物件而言,這種遍歷是沒有效果的,因為可能進行了很多次遍歷,但是他們依舊存在。因此,分代垃圾回收採用分治的思想,進行代的劃分,把不同生命週期的物件放在不同代上,不同代上採用最適合它的垃圾回收方式進行回收。

二、如何分代

如圖所示:

Java分代垃圾回收策略原理詳解

虛擬機器中的共劃分為三個代:年輕代(Young Generation)、年老代(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java類的類資訊,與垃圾收集要收集的Java物件關係不大。年輕代和年老代的劃分是對垃圾收集影響比較大的。

年輕代:

所有新生成的物件首先都是放在年輕代的。年輕代的目標就是儘可能快速的收集掉那些生命週期短的物件。年輕代分三個區。一個Eden區,兩個Survivor區(一般而言)。大部分物件在Eden區中生成。當Eden區滿時,還存活的物件將被複制到Survivor區(兩個中的一個),當這個Survivor區滿時,此區的存活物件將被複制到另外一個Survivor區,當這個Survivor區也滿了的時候,從第一個Survivor區複製過來的並且此時還存活的物件,將被複制“年老區(Tenured)”。需要注意,Survivor的兩個區是對稱的,沒先後關係,所以同一個區中可能同時存在從Eden複製過來的物件,和從前一個Survivor複製過來的物件,而複製到年老區的只有從第一個Survivor區過來的物件。而且,Survivor區總有一個是空的。同時,根據程式需要,Survivor區是可以配置為多個的(多於兩個),這樣可以增加物件在年輕代中的存在時間,減少被放到年老代的可能。

新生代有劃分為Eden、From Survivor和To Survivor三個部分,他們對應的記憶體空間的大小比例為8:1:1,也就是,為物件分配記憶體的時候,首先使用Eden空間,經過GC後,沒有被回收的會首先進入From Survivor區域,任何時候,都會保持一個Survivorq區域(From Survivor或To Survivor)完全空閒,也就是說新生代的記憶體利用率最大為90%。From Survivor和To Survivor兩個區域會根據GC的實際情況,進行互換,將From Survivor區域中的物件全部複製到To Survivor區域中,或者反過來,將To Survivor區域中的物件全部複製到From Survivor區域中。

年老代:

在年輕代中經歷了N次垃圾回收後仍然存活的物件,就會被放到年老代中。因此,可以認為年老代中存放的都是一些生命週期較長的物件。

GC過程中,當某些物件經過多次GC都沒有被回收,可能會進入到年老代。或者,當新生代沒有足夠的空間來為物件分配記憶體時,可能會直接在年老代進行分配。

持久代:

用於存放靜態檔案,如今Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者呼叫一些class,例如Hibernate等,在這種時候需要設定一個比較大的持久代空間來存放這些執行過程中新增的類。持久代大小通過-XX:MaxPermSize=<N>進行設定。

永久代實際上對應著虛擬機器執行時資料區的“方法區”,這裡主要存放類資訊、靜態變數、常量等資料。一般情況下,永久代中對應的物件的GC效率非常低,因為這裡的的大部分物件在執行都不要進行GC,它們會一直被利用,直到JVM退出。

三、什麼情況下觸發垃圾回收

由於物件進行了分代處理,因此垃圾回收區域、時間也不一樣。GC有兩種型別:Scavenge GC和Full GC。

Scavenge GC

一般情況下,當新物件生成,並且在Eden申請空間失敗時,就會觸發Scavenge GC,對Eden區域進行GC,清除非存活物件,並且把尚且存活的物件移動到Survivor區。然後整理Survivor的兩個區。這種方式的GC是對年輕代的Eden區進行,不會影響到年老代。因為大部分物件都是從Eden區開始的,同時Eden區不會分配的很大,所以Eden區的GC會頻繁進行。因而,一般在這裡需要使用速度快、效率高的演算法,使Eden去能儘快空閒出來。

對整個堆進行整理,包括Young、Tenured和Perm。Full GC因為需要對整個塊進行回收,所以比Scavenge GC要慢,因此應該儘可能減少Full GC的次數。在對JVM調優的過程中,很大一部分工作就是對於FullGC的調節。有如下原因可能導致Full GC:

  • · 年老代(Tenured)被寫滿
  • · 持久代(Perm)被寫滿
  • · System.gc()被顯示呼叫
  • ·上一次GC之後Heap的各域分配策略動態變化

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。