瞭解Java物件,簡單聊聊JVM調優分析
瞭解Java物件,簡單聊聊JVM調優分析
1、oop模型
Klass模型請看jvm底層之類載入,它是Java類的元資訊在JVM中的存在形式。而oop模型是Java物件在JVM中的存在形式,在 Java 程式執行的過程中,每建立一個新的物件,在 JVM 內部就會相應地建立一個對應型別的 oop(普通物件指標) 物件。各種 oop 類的共同基類為 oopDesc
類。
在 JVM 內部,一個 Java 物件在記憶體中的佈局可以連續分成兩部分:物件頭(instanceOopDesc) 和例項資料(成員變數)。
sychronized的底層實現就是MarkOopDesc,InstanceOopDesc對應InstanceKlass,arrayOopDesc對應ArrayKlass,typeArrayOopDesc對應TypeArrayKlass,objArrayOopDesc對應ObjArrayKlass。
2、物件的記憶體佈局
下面圖是大家眼中的記憶體佈局圖,但是還有另外一種情況,具體細節後面談。
2.1、物件頭
2.1.1、Mark Word
就是標記字,用於儲存物件自身的執行時資料,如雜湊碼(hashCode),GC分代年齡,執行緒鎖狀態標誌,執行緒持有的鎖,偏向執行緒ID,偏向時間戳等;
32bit機下佔4B,64bit機下佔8B
2.1.2、型別指標
Klass pointer :物件所屬的類的元資訊的例項指標(instanceKlass在方法區的地址)即instanceKlass在方法區的地址,即物件指向它的類後設資料的指標,虛擬機器通過這個指標來確定這個物件是哪個類的例項。
大小與指標壓縮有關係:開啟的話佔4B,關閉的話8B
2.1.3、陣列長度
如果這個物件不是陣列,佔0B,如果這個物件是陣列,佔4B。
因此可以推出一個陣列最多隻有2^32-1個元素
2.2、例項資料
儲存物件真正有效資訊,如果是基本型別,直接儲存下來,如果是引用型別,儲存的是指向堆區中物件的引用指標類的。
非靜態屬性,生成物件時就是例項資料
8種基本型別:4種整形byte、short、int、long;2種浮點型別float、double;1種Unicode編碼的字元單元的字元型char;1中Boolean型別float;
而引用型別,開啟指標壓縮佔4個位元組,未開啟指標壓縮佔8個位元組
型別 | 佔用位元組B(byte) | 佔用位數(bit) |
---|---|---|
byte | 1 | 8 |
short | 2 | 16 |
int | 4 | 32 |
long | 8 | 64 |
float | 4 | 32 |
double | 8 | 64 |
char | 2 | 16 |
boolean | 1 | 8 |
2.3、對齊填充
Java中所有的物件大小都是8位元組對齊 說白就是8的整數倍,
Hotspot的自動記憶體管理系統,要求物件的起始地址必須是8位元組的整數倍,換句話說,物件的大小必須是8位元組的整數倍,而物件頭部分正好是8位元組的整數倍(1倍或2倍),那麼如果物件大小不是8位元組整數倍怎麼辦,就用對齊填充來補充到最近的8位元組整數倍大小。
比如一個物件佔30B + JVM底層會補2B(對齊填充),湊成32位元組,達到8位元組對齊
3、計算物件大小
用jol-core包或者HSDB都可以看,區別是HSDB只能看基於普通類生成物件的大小,java中的陣列因為是執行時生成的,故它的大小隻有執行時才能知曉。
3.1、空物件
沒有例項屬性的物件
public class CountEmptyObjectSize {
public static void main(String[] args) {
CountEmptyObjectSize obj = new CountEmptyObjectSize();
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
}
結論:開啟和關閉指標壓縮大小都是相通的16B
開啟:16B = 8B(Mark word) + 4B(型別指標) + 0B(陣列長度) + 0B(例項資料) + 4B(對齊填充)
關閉:16B = 8B(Mark word) + 8B(型別指標) + 0B(陣列長度) + 0B(例項資料) + 0B(對齊填充)
3.2、普通物件
public class CountObjectSize {
int a = 10;
int b = 20;
public static void main(String[] args) {
CountObjectSize object = new CountObjectSize();
System.out.println(ClassLayout.parseInstance(object).toPrintable());
}
}
結論:開啟和關閉指標壓縮大小都是相通的16B
開啟:24B = 8B(Mark word) + 4B(型別指標) + 0B(陣列長度) + 8B(例項資料) + 4B(對齊填充)
關閉:24B = 8B(Mark word) + 8B(型別指標) + 0B(陣列長度) + 8B(例項資料) + 0B(對齊填充)
3.3、陣列物件
public class CountSimpleObjectSize {
static int[] arr = {0, 1, 2};
public static void main(String[] args) {
CountSimpleObjectSize test1 = new CountSimpleObjectSize();
System.out.printf(ClassLayout.parseInstance(arr).toPrintable());
}
}
結論:開啟和關閉指標壓縮大小都是相通的16B
開啟:32B = 8B(Mark word) + 4B(型別指標) + 4B(陣列長度) + 12B(例項資料) + 4B(對齊填充)
關閉:40B = 8B(Mark word) + 8B(型別指標) + 4B(陣列長度) + 4B(對齊填充) + 12B(例項資料) + 4B(對齊填充)
可以看到上面有兩段對齊填充,第一段是物件頭的,第二段是物件的; 即陣列物件,在關閉指標壓縮的情況下,物件頭也會進行對齊填充(8位元組對齊),這樣的話,物件的記憶體模型圖改為如下
其實不止陣列物件,在關閉指標壓縮的情況下會出現兩端填充
開啟指標壓縮,沒有例項資料情況下,陣列物件大小是16,關閉指標壓縮,陣列物件大小膨脹到24,陣列長度由4個位元組變為8個位元組,Pedding對齊4個位元組
4、指標壓縮
jdk6以後引入的技術,預設是開啟的,可以調優:-XX:+/-UseCompressedOops指標壓縮的目的就是節省記憶體,定址更高效。
可以看下:https://juejin.cn/post/6844903768077647880
對於32位機器,程式能使用的最大記憶體是4G。如果程式需要使用更多的記憶體,需要使用64位機器。
對於Java程式,在oop只有32位時,只能引用4G記憶體。因此,如果需要使用更大的堆記憶體,需要部署64位JVM。這樣,oop為64位,可引用的堆記憶體就更大了。
注:oop(ordinary object pointer),即普通物件指標,是JVM中用於代表引用物件的控制程式碼。
在堆中,32位的物件引用佔4個位元組,而64位的物件引用佔8個位元組。也就是說,64位的物件引用大小是32位的2倍。
4.1、指標壓縮的實現原理
java中所有的物件都是8位元組對齊的。所以8位元組對齊有一個規律那就是所有物件的指標後三位永遠是0
舉例
比如三個物件:
test1 = 16B
test2 = 24B
test3 = 32B
地址儲存(從0地址開始順序儲存)
test1 = 0 000
test2 = 16(十進位制)10 000
test3 = 40(十進位制)101 000
儲存的時候,後三位0抹除(儲存時指從暫存器讀出)
test1 = 0
test2 = 10
test3 = 101
使用的時候,後三位補0(使用時指存入暫存器)
test1 = 0 000
test2 = 10 000
test3 = 101 000
public class CompressedTest {
public static void main(String[] args) {
CompressedTest test = new CompressedTest();
while(true);
}
}
不開啟指標壓縮如下圖: _klass
開啟指標壓縮如下圖: _compressed_klass
對應處理方式在jdk的\openjdk\hotspot\src\share\vm\oops\oop.inline.hpp
class oopDesc {
friend class VMStructs;
private:
volatile markOop _mark;
union _metadata {
Klass* _klass; //不開啟
narrowKlass _compressed_klass; //開啟
} _metadata
...
4.2、開啟指標壓縮的情況下,一個oop(物件指標)表示的最大堆空間是多少
我們知道開啟指標壓縮的情況下,型別指標佔4位元組,也就是32位,JVM儲存時會抹掉後面的3位,也就是可以儲存32 + 3 = 35位,最大記憶體空間也就是2的35次方,32G。
32位機子,一共有2^32個狀態,一個狀態是一個記憶體地址,那麼32位機子能表示最大記憶體是4GB
64位機子,實際表示的是35位
4.3、oop(物件指標)如何擴容
前面說到JVM採用8位元組對齊,會抹掉後面的3位,如果我們讓它採用16位元組對齊,那麼是不是可以抹掉最後面的4位,oop(物件指標)所能表示的堆空間則為2的32 + 4次方即64G
4.4、為什麼沒這樣做(16位元組對齊)
1、增加了GC開銷,坦白講cpu的效能有限,現在的GC演算法處理32G的堆已經是極限了,所以還是cpu的瓶頸。64位物件引用需要佔用更多的堆空間,留給其他資料的空間將會減少,(說白了就是費空間)從而加快了GC的發生,更頻繁的進行GC。
2、降低CPU快取命中率,64位物件引用增大了,CPU能快取的oop將會更少,從而降低了CPU快取的效率。
5、聊聊JVM調優分析
5.1、調優階段
- 在專案部署到線上之前,基於可能的併發量進行預估調優
- 專案上線初期,在專案執行過程中,部署監控收集效能資料,平時分析日誌進行調優
- 線上出現OOM,、頻繁full gc,進行問題排查,做徹底的調優。
5.2、為什麼要調優
- 防止出現OOM
- 解決OOM
- 減少full gc出現的頻率
5.3、到底調什麼
- 方法區
- 虛擬機器棧
- 堆區
- 熱點程式碼緩衝區
6、案例:億級流量系統調優分析
這裡以億級流量秒殺電商系統為例:
1、如果每個使用者平均訪問20個商品詳情頁,那訪客數約等於500w(一億 / 20)
2、如果按轉化率10%來算,那日均訂單約等於50w(500w * 10%)
3、如果30%的訂單是在秒殺前兩分鐘完成的,那麼每秒產生1200筆訂單(50w * 30% / 120s)
4、訂單支付又涉及到發起支付流程、物流、優惠券、推薦、積分等環節,導致產生大量物件,這裡我們假設整個支付流程生成的物件約等於20K,那每秒在Eden區生成的物件約等於20M(1200筆 * 20K)
5、在生產環境中,訂單模組還涉及到百萬商家查詢訂單、改價、包郵、發貨等其他操作,又會產生大量物件,我們放大10倍,即每秒在Eden區生成的物件約等於200M(其實這裡就是在大併發時刻可以考慮服務降級的地方,架構其實就是取捨)
這裡的假設資料都是大部分電商系統的通用概率,是有一定代表性的。
我機器記憶體32G
堆區:最小是實體記憶體的1/64,最大是實體記憶體的1/4
假設一個操作需要3秒完成,就有600M進入Eden區,
所以差不多每2700 / 200 = 14秒發生一次young gc,
這600M物件還在用,無法被回收,from to區放不下, 此時會觸發空間擔保直接進入老年代(5400M),所以大概(9*14 = 126)2分鐘就會發生一次full gc。
所以將這些物件儘量在young gc階段被回收掉,少觸發full gc。當然加機器也是一種辦法。
後期再總結具體調優方式吧。。。
相關文章
- "簡單"的jvm調優JVM
- “簡單”的jvm調優JVM
- 簡單JVM調優經歷JVM
- Java jvm 診斷調優JavaJVM
- JVM原理講解和調優JVM
- 11 個簡單的 Java 效能調優技巧Java
- 探探Java之 JVM GC與調優JavaJVMGC
- JVM調優JVM
- 《java學習三》jvm效能優化-------調優JavaJVM優化
- JVM效能調優,記憶體分析工具JVM記憶體
- WebSocket 簡單瞭解Web
- JWT簡單瞭解JWT
- 簡單瞭解procmailAI
- 面試官:簡單聊聊 Go 逃逸分析?面試Go
- JVM調優總結-調優方法JVM
- JVM調優策略JVM
- JVM之調優及常見場景分析JVM
- 效能調優(cpu/IO/JVM記憶體分析)JVM記憶體
- 簡單聊聊Java中的ReentrantLockJavaReentrantLock
- JVM調優總結(十)-調優方法JVM
- Golang介面簡單瞭解Golang
- 防火牆-簡單瞭解防火牆
- 簡單瞭解組策略
- JVM調優推薦JVM
- JVM調優淺談JVM
- jvm系列(七):jvm調優-工具篇JVM
- Java面試題中高階進階(JVM調優篇)Java面試題JVM
- Java String 物件,你瞭解多少?Java物件
- Node.js簡單瞭解Node.js
- 簡單瞭解負載均衡負載
- 快應用簡單瞭解
- Java教程學習入門影片原始碼課件:JVM調優講解Java原始碼JVM
- 簡單聊聊WebSocketWeb
- 簡單聊聊模組
- JVM調優:HotSpot JVM垃圾收集器JVMHotSpot
- 11個簡單的Java效能調優技巧,傻瓜都能學會!Java
- JVM 調優命令&工具使用JVM
- JVM 引數調優(qbit)JVM