1. 程式人生 > >JVM從零開始 -垃圾回收機制以及記憶體分代模型

JVM從零開始 -垃圾回收機制以及記憶體分代模型

JVM中垃圾回收的判定標準

最終目的是將記憶體中無用的物件回收掉。具體的判定方法有:

  • 引用計數法,不採用,指的是維護物件被引用的次數,次數為0則意味著是垃圾。
  • 可達性演算法-GC Roots tracing,指的是從GC Roots開始往下遍歷所有引用的物件,(每個GC Root就是一個樹狀圖),所有被引用到的物件就是需要存活的物件,其他物件可以被回收。GC Root指的是,虛擬機器棧(棧幀中的本地變量表)中引用的物件,方法區中非基本型別的類靜態變數(一個地址)所引用的物件,本地方法棧中JNI(即一般說的Native方法)引用的物件。

JVM中記憶體相關引數

  • -Xms Java堆記憶體初始大小
  • -Xmx Java堆記憶體最大大小
  • -Xmn Java堆記憶體中的新生代大小,扣除它就是老年代大小
  • -XX:PermSize(1.8之後:-XX:MetaspaceSize) 永久代初始大小
  • -XX:MaxPerSize(1.8之後:-XX:MaxMetaspaceSize) 永久代最大大小
  • -Xss 每個執行緒的棧記憶體大小

注:通常情況下,Xms和Xmx,-XX:PermSize和-XX:MaxPerSize都會設定為一樣。

  • -XX:MaxTenuringThreshold 多少歲進入老年代-預設15
  • -XX:PretenureSizeThreshold 超過多少位元組的大物件直接進入老年代
  • -XX:HandlePromotionFailure MinorGC時,如果老年代剩餘空間小於新生代物件總大小,但是如果大於之前平均進入老年代物件的大小,是否嘗試進行MinorGC(預設開啟)
  • -XX:SurvivorRatio=8 Eden區的比例
  • -XX:+UseParNewGC 指定使用ParNewGC
  • -XX:ParallelGCThreads,指定ParNewGC執行緒數量,一般不指定ParNewGC會自動根據CPU核數適配

上面看不懂的引數不要深究,等下提到回過頭再來看,這裡只是將所有引數羅列出來方便查詢。

JVM中的記憶體分代模型

JVM中,將物件在記憶體中分為了三代:

  • 年輕代:很快被回收的物件,存在於堆,具體還在記憶體中分為了1個eden區,和2個survivor區。
  • 老年代:長期存在的物件,存在於堆
  • 永久代:指的就是方法區(存放Class元資料),回收條件較苛刻,需滿足:該類所有例項物件所有已經從堆記憶體被回收,該類classLoader已經被回收,該類Class物件沒有任何引用

為什麼要分代勒,因為針對每個年齡代,都有不同的垃圾回收演算法,以及記憶體分配機制。如果將所有物件放在一起,第一是會造成頻繁遍歷判斷回收的開銷,第二是會造成複製、移動的開銷,為什麼會有複製、移動,因為回收記憶體必然會造成記憶體碎片,而記憶體碎片會導致空間浪費,所以必須通過複製、移動來清理隨便,使得空閒記憶體連續。

JVM中具體的記憶體分配模型

如上圖,至於年輕代為什麼要如此分配,與特定的回收演算法有關。

物件在記憶體分代中如何流轉

年輕代

大部分物件(除了元資料,如"xxx"字串的地址)剛建立的時候都會分配在年輕代的Eden區,只要年輕代空間不夠就會觸發MinorGC(只回收年輕代記憶體)。

老年代

老年代的物件都是從年輕代根據一定的規則流轉過來的。 具體有幾類流轉方式:

  • 超過指定年齡(引數-XX:MaxTenuringThreshold 配置,預設15),這裡年齡指的是沒有被垃圾回收,存活下來一次理解為增加一歲。流轉到老年代。

  • 大物件直接進入,超過引數指定位元組數(-XX:PretenureSizeThreshold)設定的位元組數的大物件會直接進入老年代,這是因為物件越大,複製開銷就越大。

  • 動態年齡判斷規則進入,意思是不一定要到指定年齡再流轉到15,如果某一年齡以上的物件到達一定大小,也會提前進入老年代。當躲過一輪GC的物件加起來超過surrvivor區50%,如年齡1+年齡2+年齡n一直累加,直到年齡n的時候發現加起來超過了surrvivor空間的50%,則年齡n以上的物件直接進入老年代

  • 年輕代放不下,直接進入老年代。這裡面有一定的規則,等會介紹。

當年輕代分配擔保條件不滿足時或滿足了年輕代分配擔保條件但在實施過程中發現老年代空間不夠時觸發fullGC。

永久代

永久代一般

睡覺了先,上面總結的不是很好,明