Java虛擬機這一塊 —— JVM搞對象(對象處理、內存分配策略)

問題?

  1. JVM碰到對象是怎樣處理的?
  2. JVM是如何進行對象訪問的?
  3. JVM中對象是如何分配的?

虛擬機中的對象

對象的分配

虛擬機遇到一條 new 指令時:根據 new 的參數是否能在常量池中定位到一個類的符號引用,如果沒有,說明還未定義該類,拋出 ClassNotFoundException;

在這裏插入圖片描述

1.檢查加載

先執行相應的類加載過程。如果沒有,則進行類加載

2.分配內存

根據方法區的信息確定爲該類分配的內存空間大小

分配內存方式

指針碰撞 (java堆內存空間規整的情況下使用)
  • 接下來虛擬機將爲新生對象分配內存。爲對象分配空間的任務等同於把一塊確定大小的內存從 Java 堆中劃分出來。
  • 如果 Java 堆中內存是絕對規整的,所有用過的內存都放在一邊,空閒的內存放在另一邊,中間放着一個指針作爲分界點的指示器,那所分配內存就僅僅是把那個指針向空閒空間那邊挪動一段與對象大小相等的距離,這種分配方式稱爲“指針碰撞”。
空閒列表 (java堆空間不規整的情況下使用)
  • 如果 Java 堆中的內存並不是規整的,已使用的內存和空閒的內存相互交錯,那就沒有辦法簡單地進行指針碰撞了,虛擬機就必須維護 一個列表,記錄上哪些內存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,並更新列表上的記錄,這種 分配方式稱爲“空閒列表”。
  • 選擇哪種分配方式由 Java 堆是否規整決定,而 Java 堆是否規整又由所採用的垃圾收集器是否帶有壓縮整理功能決定。

分配內存存在的問題

併發安全

除如何劃分可用空間之外,還有另外一個需要考慮的問題是對象創建在虛擬機中是非常頻繁的行爲,即使是僅僅修改一個指針所指向 的位置,在併發情況下也並不是線程安全的,可能出現正在給對象 A 分配內存,指針還沒來得及修改,對象 B 又同時使用了原來的指 針來分配內存的情況。

CAS 機制

解決這個問題有兩種方案,一種是對分配內存空間的動作進行同步處理——實際上虛擬機採用 CAS 配上失敗重試的方式保證更新操作 的原子性;

本地線程分配緩衝
  • 另一種是把內存分配的動作按照線程劃分在不同的空間之中進行,即每個線程在 Java 堆中預先分配一小塊私有內存,也就是本地線程 分配緩衝(ThreadLocalAllocationBuffer,TLAB),如果設置了虛擬機參數 -XX:+UseTLAB,在線程初始化時,同時也會申請一塊指定大小 的內存,只給當前線程使用,這樣每個線程都單獨擁有一個 Buffer,如果需要分配內存,就在自己的 Buffer 上分配,這樣就不存在競 爭的情況,可以大大提升分配效率,當 Buffer 容量不夠的時候,再重新從 Eden 區域申請一塊繼續使用。
  • TLAB 的目的是在爲新對象分配內存空間時,讓每個 Java 應用線程能在使用自己專屬的分配指針來分配空間 (Eden 區,默認 Eden 的 1%), 減少同步開銷。
  • TLAB 只是讓每個線程有私有的分配指針,但底下存對象的內存空間還是給所有線程訪問的,只是其它線程無法在這個區域分配而已。 當一個 TLAB 用滿(分配指針 top 撞上分配極限 end 了),就新申請一個 TLAB。

在這裏插入圖片描述

3.內存空間初始化

(注意不是構造方法)內存分配完成後,虛擬機需要將分配到的內存空間都初始化爲零值(如 int 值爲 0,boolean 值爲 false 等等)。
這 一步操作保證了對象的實例字段在 Java 代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數據類型所對應的零值。

4.設置

接下來,虛擬機要對對象進行必要的設置,例如這個對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的 GC 分代年齡等信息。這些信息存放在對象的對象頭之中。

5.對象初始化

在上面工作都完成之後,從虛擬機的視角來看,一個新的對象已經產生了,但從 Java 程序的視角來看,對象創建纔剛剛開始,所有的字段都還爲零值。所以,一般來說,執行 new 指令之後會接着把對象按照程序員的意願進行初始化,這樣一個真正可用的對象纔算完 全產生出來。

對象的內存佈局

在 HotSpot 虛擬機中,對象在內存中存儲的佈局可以分爲 3 塊區域:對象頭(Header)、實例數據(InstanceData)和對齊填充(Padding)。
在這裏插入圖片描述

  • 對象頭包括兩部分信息,第一部分用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC 分代年齡、鎖狀態標誌、線程持有的 鎖、偏向線程 ID、偏向時間戳等。
  • 對象頭的另外一部分是類型指針,即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。
  • 第三部分對齊填充並不是必然存在的,也沒有特別的含義,它僅僅起着佔位符的作用。由於 HotSpotVM 的自動內存管理系統要求對對 象的大小必須是 8 字節的整數倍。對象正好是 9 字節的整數,所以當對象其他數據部分(對象實例數據)沒有對齊時,就需要通過對 齊填充來補全。

對象的訪問定位

建立對象是爲了使用對象,我們的 Java 程序需要通過棧上的 reference 數據來操作堆上的具體對象。目前主流的訪問方式有使用句柄和 直接指針兩種。

句柄

如果使用句柄訪問的話,那麼 Java 堆中將會劃分出一塊內存來作爲句柄池,reference 中存儲的就是對象的句柄地址,而句柄中包含了 對象實例數據與類型數據各自的具體地址信息。

直接指針

  • 如果使用直接指針訪問, reference 中存儲的直接就是對象地址。
  • 這兩種對象訪問方式各有優勢,使用句柄來訪問的最大好處就是 reference 中存儲的是穩定的句柄地址,在對象被移動(垃圾收集時移 動對象是非常普遍的行爲)時只會改變句柄中的實例數據指針,而 reference 本身不需要修改。
  • 使用直接指針訪問方式的最大好處就是速度更快,它節省了一次指針定位的時間開銷,由於對象的訪問在 Java 中非常頻繁,因此這類 開銷積少成多後也是一項非常可觀的執行成本。
  • 對 SunHotSpot 而言,它是使用直接指針訪問方式進行對象訪問的。

在這裏插入圖片描述

堆內存分配策略

堆進一步劃分

新生代(PSYoungGen)
  • Eden空間
  • From Survivor空間
    設置 Survivor 是爲了減少送到老年代的對象
  • To Survivor空間
    設置兩個 Survivor 區是爲了解決碎片化的問題(複製回收算法)
老年代(ParOldGen)

堆中參數配置: 新生代大小: -Xmn20m 表示新生代大小20m(初始和最大)
-XX:SurvivorRatio=8 表示Eden和Survivor的比值, 缺省爲8 表示 Eden:From:To= 8:1:1; 改爲2時 Eden:From:To= 2:1:1

1.對象優先在 Eden 區分配

  • 虛擬機參數: -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails
  • -XX:+PrintGCDetails 打印垃圾回收日誌,程序退出時輸出當前內存的分配情況
  • 注意:新生代初始時就有大小
  • 大多數情況下,對象在新生代 Eden 區中分配。當 Eden 區沒有足夠空間分配時,虛擬機將發起一次 MinorGC。

2.大對象直接進入老年代

  • 虛擬機參數:-Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:PretenureSizeThreshold=4m -XX:+UseSerialGC
  • PretenureSizeThreshold 參數只對 Serial 和 ParNew 兩款收集器有效。規定多大的對象纔可以在老年代中進行分配,一旦大於該參數值便可直接進入老年代
  • 最典型的大對象是那種很長的字符串以及數組。這樣做的目的:
    1.避免大量內存複製
    2.避免提前進行垃圾回收,明明內存有空間進行分配。

3.長期存活對象進入老年代

如果對象在 Eden 出生並經過第一次 MinorGC 後仍然存活,並且能被 Survivor 容納的話,將被移動到 Survivor 空間中,並將對象年齡設爲 1,對象在 Survivor 區中每熬過一次 MinorGC,年齡就增加 1,當它的年齡增加到一定程度(默認爲 15)時,就會被晉升到老年代中。

4.對象年齡動態判定

如果在 Survivor 空間中相同年齡所有對象大小的綜合大於 Survivor 空間的一半,年齡大於或等於該年齡的對象就可以直接進入老年代

5.空間分配擔保

在發生 MinorGC 之前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代所有對象總空間,如果這個條件成立,那麼 MinorGC 可以確保是安全 的。如果不成立,則虛擬機會查看 HandlePromotionFailure 設置值是否允許擔保失敗。如果允許,那麼會繼續檢查老年代最大可用的連續空間是否大於歷 次晉升到老年代對象的平均大小,如果大於,將嘗試着進行一次 MinorGC,儘管這次 MinorGC 是有風險的,如果擔保失敗則會進行一次 FullGC;如果小 於,或者 HandlePromotionFailure 設置不允許冒險,那這時也要改爲進行一次 FullGC。

分配圖示
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章