目錄頁:https://mp.csdn.net/postedit/95937156
1. 小聲嗶嗶
作爲一個程序員找不到對象很正常,但是我們寫的代碼要是找不到對象就出大問題,所以瞭解Jvm對象的相關知識作爲我的第二部分開始學習,下面有記錄的不對的地方請指正。
2.Jvm對象分配
國家不分配對象,但是Jvm分配對象。
我們在不適用spring等框架的時候,創建一個對象的最簡單方式就是使用new,Jvm虛擬機在碰到new關鍵字分別做了以下幾個步驟:檢查類加載->分配內存->內存空間初始化->對象設置->對象初始化。
- 檢查類加載:若需要new的對象的類還沒有加載,則必須先進行類加載。
- 分配內存:對象所需的內存大小在類加載後就可以完全確認。我們都知道,對象的內存是在堆中劃分出來的,對象的內存分配有兩種方式:指針碰撞和空閒列表。
若java堆中的內存是絕對規整的,空閒內存在一邊,被佔用的內存在一邊,指針指向內存空閒和被佔用區域的分界點,分配內存時僅僅需要將指針向空閒區域移動一段與對象所需內存大小相同的距離即可,這種分配方式就是指針碰撞。
若堆中的內存是不規整的,那麼虛擬機就必須維護一個列表,列表中記錄哪些內存是可用的,在分配內存時只需找到一個足夠大的內存區域分配並更新列表中記錄即可,這種內存分配方式就是空閒列表。
選擇哪種方式進行內存分配依賴於堆內存是否規整,而堆內存是否規整依賴於GC機制是否有壓縮整理功能。
需要注意的是這兩種內存分配的方式都會有併發安全的問題,解決這個問題有兩種方式:1.Jvm虛擬機是採用CAS指令來保證併發安全。2.每個線程在Java堆中預先分配一小塊內存,成爲本地線程分配緩衝(TLAB),哪個線程要分配內存就在哪個線程的TLAB中進行分配。虛擬機是否使用TLAB通過-XX:+/-UseTLAB參數決定。
- 內存空間初始化:初始化參數默認值所佔用的內存空間。
- 對象設置:這裏的設置主要是設置這個對象是哪個類的實例,對象的hash碼,對象的GC分代信息等。這些信息存放在對象頭中。
- 對象初始化:調用構造方法進行對象初始化
3. 對象內存佈局
HotSpot虛擬機中內存佈局分配三個:對象頭,實例數據和對齊填充。
對象頭中包括兩個信息:
- 運行時數據:哈希碼(HashCode),鎖狀態標識,鎖狀態標識,GC分代信息等。
- 類型指針:對象指向它的類元數據指針,虛擬機通過該指針確認對象是哪個類的實例。
實例數據:程序代碼中定義的各種類型的字段內容,包括父類中繼承下來的也需要記錄。
對齊填充:由於HotSpot自動內存管理系統要求對象地址必須是8字節的整數倍 ,對象頭正好是8字節的倍數,因此當實例數據部分不是8的整數倍時就需要對齊填充來補全。
4. 對象訪問方式
對象訪問方式有兩種:
- 句柄訪問:Java中會分配一個內存地址來作爲句柄池,訪問對象時需要先到句柄池中找,然後找到對象的實例數據和類型數據
- 直接指針:訪問對象時指針指向的直接就是對象的實例數據。很顯然直接指針訪問效率更高HotSpot使用的就是直接指針。
5. 堆內存分配策略
堆內存分爲新生代和老年代,新生代又分爲Eden空間,From Survivor空間和To Survivor空間。新生代中三個區域默認分配比例爲8:1:1,可以通過參數-XX:SurvivorRatio=8來控制。
堆內存分配策略:
- 對象優先在Eden區分配
- 大對象直接進入老年代:Eden剩餘空間不足,From區和To區也沒有足夠的空間,則對象直接進入老年代。
package com.jvm.coline.part2;
public class BigObjectDemo {
private static final int BYTE_1MB = 1024*1024;
public static void main (String[] arg0){
byte[] b1,b2,b3;
b1 = new byte[2*BYTE_1MB];
b2 = new byte[2*BYTE_1MB];
b3 = new byte[5*BYTE_1MB];
}
}
啓動參數配置如下
-Xmx20m -Xms20m -Xmn10m -XX:+PrintGCDetails
輸出結果:
可以看到新生代容納不下的5M內存直接進入到了老年代。
也可以使用參數:-XX:PretenureSizeThreshold=3m來設置內存大於多少的直接進入老年代。
- 長期存活的對象進入老年代:GC年齡大於15的內存會被放置到老年代
- 動態對象年齡判定:from區或to區中內存佔用已達到50%,則內存提前放置到老年代 。
- 空間分配擔保:默認認爲分配到老年代的內存空間是夠用的,增加效率
6. 思維導圖
參考資料:周志明大神-《深入理解Java虛擬機 JVM高級特性與最佳實踐》