物件存在位置

N1ce2cu發表於2024-07-17

物件優先在 Eden 分配

  • 堆分為新生代和老年代,新生代用於存放使用後就要被回收的物件(朝生夕死),老年代用於存放生命週期比較長的物件。

  • 建立的大部分物件,都屬於生命週期較短的物件,所以會存放在新生代。新生代又細分 Eden、From Survivor、To Survivor,物件會優先在 Eden 區分配。

  • 隨著物件的不斷建立,Eden 剩餘地記憶體空間就會越來越少,隨後就會觸發 Minor GC,於是 JVM 會把 Eden 區存活的物件轉入 From Survivor 空間。

  • Minor GC 後,又建立的新物件會繼續往 Eden 區分配。

  • 於是,隨著新物件的建立,Eden 的剩餘記憶體空間就會越來越少,又會觸發 Minor GC,此時,JVM 會對 Eden 區和 From Survivor 區中的物件進行存活判斷,對於存活的物件,會轉移到 To Survivor 區。

  • 下一次 Minor GC,存活的物件又會從 To 到 From,這樣就總有一個 Survivor 區是空的,而另外一個是無碎片的。

大物件直接進入老年代

  • 對於上面的流程,也有例外的存在,如果一個物件很大,一直在 Survivor 空間複製來複制去,就會很浪費效能,所以這些大物件會直接進入老年代。這種策略的目的是減少垃圾回收時的複製開銷。

  • 可以透過 -XX:PretenureSizeThreshold 引數設定直接分配大物件到老年代的閾值。如果物件的大小超過這個閾值,它將直接在老年代中分配。例如,如果想將閾值設定為 1MB(1024KB),可以這樣設定:
-XX:PretenureSizeThreshold=1048576

長期存活的物件將進入老年代

  • 物件在每次從一個 Survivor 區轉移到另外一個 Survivor 區時,它的年齡就會增加。當物件的年齡達到一定閾值(預設為 15),則它會被轉移到老年代。可以用 -XX:PretenureSizeThreshold=10 來設定年齡。

  • 虛擬機器為了給物件計算他到底經歷了幾次 Minor GC,會給每個物件定義了一個物件年齡計數器。如果物件在 Eden 中經過第一次 Minor GC 後仍然存活,移動到 Survivor 空間年齡加 1,在 Survivor 區中每經歷過 Minor GC 後仍然存活年齡再加 1。年齡到了 15,就到了老年代。

動態年齡判斷

  • 除了年齡達到 MaxTenuringThreshold,還有另外一個方式進入老年代,那就是動態年齡判斷:JVM 會檢查每個年齡段的物件大小,並估算它們在 Survivor 空間中所佔的總體積。JVM 會選擇一個最小的年齡,使得該年齡及以上的物件可以填滿 Survivor 空間的一部分(通常小於總空間的一半),然後將這些物件晉升到老年代。

  • 比如 Survivor 是 100M,Hello1 和 Hello2 都是 3 歲,且總和超過了 50M,Hello3 是 4 歲,這個時候,這三個物件都將到老年代。

空間分配擔保

  • 存活的物件會放入另外一個 Survivor 空間,如果這些存活的物件比 Survivor 空間還大呢

整個流程如下:

  • Minor GC 之前,JVM 會先檢查老年代最大可用的連續空間是否大於新生代所有物件的總空間,如果大於,則發起 Minor GC。
  • 如果小於,則看 HandlePromotionFailure 有沒有設定,如果沒有設定,就發起 Full GC。
  • 如果設定了 HandlePromotionFailure,則看老年代最大可用的連續空間是否大於歷次晉升到老年代物件的平均大小,如果小於,就發起 Full GC。
  • 如果大於,發起 Minor GC。Minor GC 後,看 Survivor 空間是否足夠存放存活物件,如果不夠,就放入老年代,如果夠放,就直接存放 Survivor 空間。如果老年代都不夠放存活物件,擔保失敗(Handle Promotion Failure),發起 Full GC。

HandlePromotionFailure 的作用,當設定為 true 時(預設值),JVM 會嘗試繼續 Minor GC,即使老年代空間不足以容納所有需要晉升的物件。JVM 會嘗試清理更多的老年代空間或者採用其他措施來應對空間不足的情況。避免因為老年代空間不足而過早觸發 Full GC(全堆回收)。Full GC 通常比 Minor GC 更耗時,會導致更長時間的停頓。

棧和方法區

  • Java 建立的物件幾乎都在堆中,這包括透過 new 關鍵字建立的物件和陣列。而物件的引用,通常存放在棧中,比如說當你在方法中宣告一個變數 MyClass obj = new MyClass(); 時,變數 obj(一個指向堆中物件的引用)儲存在棧上。

  • 方法區用於儲存已被 JVM 載入的類資訊、常量、靜態變數以及即時編譯器編譯後的程式碼。Java 8 中,永久代被元空間(Metaspace)所取代。元空間使用本地記憶體(作業系統的記憶體),而非 JVM 記憶體。

相關文章