1.在64位平台的HotSpot中使用32位指針,內存使用會多出1.5倍左右,使用較大指針在主內存和緩存之間移動數據,占用較大寬帶,同時GC也會承受較大壓力
2.為了減少64位平台下內存的消耗,啟用指針壓縮功能
3.在jvm中,32位地址表示4G個對象的指針,在4G-32G堆內存范圍內,可以通過編碼、解碼方式進行優化,使得jvm可以支持更大的內存配置
4.堆內存小於4G時,不需要啟用指針壓縮,jvm會直接去除高32位地址,即使用低虛擬地址空間
5.堆內存大於32G時,壓縮指針會失效,會強制使用64位(即8字節)來對java對象尋址,這就會出現1的問題,所以堆內存不要大於32G為好
先放出結論:
如果配置最大堆內存超過 32 GB(當 JVM 是 8 字節對齊),那么壓縮指針會失效。 但是,這個 32 GB 是和字節對齊大小相關的,也就是-XX:ObjectAlignmentInBytes配置的大小(默認為8字節,也就是 Java 默認是 8 字節對齊)。-XX:ObjectAlignmentInBytes可以設置為 8 的整數倍,最大 128。也就是如果配置-XX:ObjectAlignmentInBytes為 24,那么配置最大堆內存超過 96 GB 壓縮指針才會失效。
壓縮指針這個屬性默認是打開的,可以通過-XX:-UseCompressedOops關閉。
首先說一下為何需要壓縮指針呢?32 位的存儲,可以描述多大的內存呢?假設每一個1代表1字節,那么可以描述 0~2^32-1 這 2^32 字節也就是 4 GB 的內存。

但是呢,Java 默認是 8 字節對齊的內存,也就是一個對象占用的空間,必須是 8 字節的整數倍,不足的話會填充到 8 字節的整數倍。也就是其實描述內存的時候,不用從 0 開始描述到 8(就是根本不需要定位到之間的1,2,3,4,5,6,7)因為對象起止肯定都是 8 的整數倍。所以,2^32 字節如果一個1代表8字節的話,那么最多可以描述 2^32 * 8 字節也就是 32 GB 的內存。

這就是壓縮指針的原理。如果配置最大堆內存超過 32 GB(當 JVM 是 8 字節對齊),那么壓縮指針會失效。 但是,這個 32 GB 是和字節對齊大小相關的,也就是-XX:ObjectAlignmentInBytes配置的大小(默認為8字節,也就是 Java 默認是 8 字節對齊)。-XX:ObjectAlignmentInBytes可以設置為 8 的整數倍,最大 128。也就是如果配置-XX:ObjectAlignmentInBytes為 24,那么配置最大堆內存超過 96 GB 壓縮指針才會失效。
編寫程序測試下:
A a = new A();
System.out.println("------After Initialization------\n" + ClassLayout.parseInstance(a).toPrintable());
首先,以啟動參數:-XX:ObjectAlignmentInBytes=8 -Xmx16g執行:
------After Initialization------
com.hashjang.jdk.TestObjectAlign$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 48 72 06 00 (01001000 01110010 00000110 00000000) (422472)
12 4 (alignment/padding gap)
16 8 long A.d 0
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total
可以看到類型字大小為 4 字節48 72 06 00 (01001000 01110010 00000110 00000000) (422472),壓縮指針生效。
首先,以啟動參數:-XX:ObjectAlignmentInBytes=8 -Xmx32g執行:
------After Initialization------
com.hashjang.jdk.TestObjectAlign$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) a0 5b c6 00 (10100000 01011011 11000110 00000000) (12999584)
12 4 (object header) b4 02 00 00 (10110100 00000010 00000000 00000000) (692)
16 8 long A.d 0
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
可以看到類型字大小為 8 字節,壓縮指針失效:
a0 5b c6 00 (10100000 01011011 11000110 00000000) (12999584)
b4 02 00 00 (10110100 00000010 00000000 00000000) (692)
修改對齊大小為 16 字節,也就是以-XX:ObjectAlignmentInBytes=16 -Xmx32g執行:
------After Initialization------
com.hashjang.jdk.TestObjectAlign$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 48 72 06 00 (01001000 01110010 00000110 00000000) (422472)
12 4 (alignment/padding gap)
16 8 long A.d 0
24 8 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 4 bytes internal + 8 bytes external = 12 bytes total
可以看到類型字大小為 4 字節48 72 06 00 (01001000 01110010 00000110 00000000) (422472),壓縮指針生效。
開啟-XX:+UseCompressedOops后對象計算有三種模式

(1) 如果堆的高位地址小於32G,說明不需要基址(base)就能定位堆中任意對象,這種模式也叫做Zero-based Compressed Oops Mode
(2) 如果堆高位大於等於32G,說明需要基地址,這時如果堆大小小於4G,說明基址+偏移能定位堆中任意對象
(3) 如果堆處於4G到32G的范圍,這時只能通過基址+偏移x縮放(scale)才能定位堆中任意對象
如果有shift的存在,對象地址還必須8字節對齊8,如果不幸堆大於32G,那么無法使用壓縮對象指針。
為什么要引入壓縮指針(明白的跳過)
先要明白:
32位操作系統可以尋址到多大內存 答:4g 因為 2^32=4 * 1024 * 1024=4g
64位呢?答:近似無窮大
為什么要用64位操作系統 答:因為連你家電腦的內存都不止4g了吧,你用8g的內存在32位電腦上是只有4g有效的,而4g滿足不了我們的需求
可是用64位有些那些問題?
答:64位過長,給我們尋址帶寬和對象內引用造成了負擔
什么負擔?往下看!
同一個對象存在堆里會花費更多的空間!!!!
口說無憑,首先我們計算下同一個對象在不同操作系統的堆中存放的大小
下面的東西是一個對象占用的字節數,
對象頭
32位系統,占用 8 字節(markWord4字節+kclass4字節)
64位系統,開啟 UseCompressedOops(壓縮指針)時,占用 12 字節,否則是16字節(markWord8字節+kclass8字節,開啟時markWord8字節+kclass4字節)
實例數據
boolean 1
byte 1
short 2
char 2
int 4
float 4
long 8
double 8
引用類型
32位系統占4字節 (因為此引用類型要去方法區中找類信息,所以地址為32位即4字節同理64位是8字節)
64位系統,開啟 UseCompressedOops時,占用4字節,否則是8字節
對齊填充
如果對象頭+實例數據的值不是8的倍數,那么會補上一些,補夠8的倍數
好了開始舉例
假設有一個對象
class A{
int a;//基本類型
B b;//引用類型
}
32位操作系統 花費的內存空間為
對象頭-8字節 + 實例數據 int類型-4字節 + 引用類型-4字節+補充0字節(16是8的倍數) 16個字節
64位操作系統
對象頭-16字節 + 實例數據 int類型-4字節 + 引用類型-8字節+補充4字節(28不是8的倍數補充4字節到達32字節) 32個字節
同樣的對象需要將近兩倍的容量,(實際平均1.5倍),所以需要開啟壓縮指針:
64位開啟壓縮指針 對象頭-12字節 + 實例數據 int類型-4字節 + 引用類型-4字節+補充4字節=24個字節
開啟后可以減緩堆空間的壓力(同樣的內存更不容易發生oom)
壓縮指針是怎么實現的
JVM的實現方式是
不再保存所有引用,而是每隔8個字節保存一個引用。例如,原來保存每個引用0、1、2…,現在只保存0、8、16…。因此,指針壓縮后,並不是所有引用都保存在堆中,而是以8個字節為間隔保存引用。
在實現上,堆中的引用其實還是按照0x0、0x1、0x2…進行存儲。只不過當引用被存入64位的寄存器時,JVM將其左移3位(相當於末尾添加3個0),例如0x0、0x1、0x2…分別被轉換為0x0、0x8、0x10。而當從寄存器讀出時,JVM又可以右移3位,丟棄末尾的0。(oop在堆中是32位,在寄存器中是35位,2的35次方=32G。也就是說,使用32位,來達到35位oop所能引用的堆內存空間)
仔細看圖~ 仔細看圖 ~仔細看圖

哪些信息會被壓縮?
1.對象的全局靜態變量(即類屬性)
2.對象頭信息:64位平台下,原生對象頭大小為16字節,壓縮后為12字節
3.對象的引用類型:64位平台下,引用類型本身大小為8字節,壓縮后為4字節
4.對象數組類型:64位平台下,數組類型本身大小為24字節,壓縮后16字節
哪些信息不會被壓縮?
1.指向非Heap的對象指針
2.局部變量、傳參、返回值、NULL指針
總結:
在JVM中(不管是32位還是64位),對象已經按8字節邊界對齊了。對於大部分處理器,這種對齊方案都是最優的。所以,使用壓縮的oop並不會帶來什么損失,反而提升了性能。
壓縮指針32g指針失效問題
講到這應該很明了了,因為寄存器中3的32次方只能尋址到32g左右(不是准確的32g,有可能在31g就發生指壓縮失效),所以當你的內存超過32g時,jvm就默認停用壓縮指針,用64位尋址來操作,這樣可以保證能尋址到你的所有內存,但這樣所有的對象都會變大,實際上未開啟開啟后的比較,40g的對象存儲個數比不上30g的存儲個數
jvm 對象的指針壓縮 32G內存指針壓縮失效 - 知乎 (zhihu.com)
學習於:jvm壓縮指針原理以及32g內存壓縮指針失效詳解_超負荷運轉-CSDN博客_壓縮指針
為什么壓縮指針超過32G失效
首先是最基本的:什么東西被壓縮了?
哪些信息會被壓縮?
1.對象的全局靜態變量(即類屬性)
2.對象頭信息:64位平台下,原生對象頭大小為16字節,壓縮后為12字節
3.對象的引用類型:64位平台下,引用類型本身大小為8字節,壓縮后為4字節
4.對象數組類型:64位平台下,數組類型本身大小為24字節,壓縮后16字節
主要是對象頭里面的kclass指針,即指向方法區的類信息的指針,由8字節變為4字節。 還有就是引用類型指針也由8字節變為4字節。
壓縮后的好處:
jvm指針壓縮的簡單理解_HaiQ~~的博客-CSDN博客
1。減緩GC的壓力,即每個對象的大小都變小了,就不需要那么頻繁的GC了。
2。降低CPU緩存的命中率。即CPU緩存本身的大小就小的多,如果采用八字節,CPU能緩存的oop(普通對象指針)肯定比四字節少,從而降低命中率。
指針壓縮的具體底層實現:
一種理解:
當開啟指針壓縮后,KlassPointer的尋址極限是4 byte × 8 bit=32 bit,即KlassPointer可以存放2^32(=4G)個內存單元地址。
因為每個對象的長度一定是8的整數倍,所以KlassPointer每一數位存放的一定是8的整數倍的地址,即0/8/16/24/32/40/48/64……,也就是4G × 8 = 32G。當分配給JVM的內存空間大於32G時,KlassPointer將無法尋找大於32G的內存地址,因此設置的壓縮指針將失效。
第二種理解:
JVM的實現方式是
不再保存所有引用,而是每隔8個字節保存一個引用。例如,原來保存每個引用0、1、2…,現在只保存0、8、16…。因此,指針壓縮后,並不是所有引用都保存在堆中,而是以8個字節為間隔保存引用。
在實現上,堆中的引用其實還是按照0x0、0x1、0x2…進行存儲。只不過當引用被存入64位的寄存器時,JVM將其左移3位(相當於末尾添加3個0),例如0x0、0x1、0x2…分別被轉換為0x0、0x8、0x10。而當從寄存器讀出時,JVM又可以右移3位,丟棄末尾的0。(oop在堆中是32位,在寄存器中是35位,2的35次方=32G。也就是說,使用32位,來達到35位oop所能引用的堆內存空間)
我感覺主要就是2的32次方等於4G,然后8個bit的長度,即2的三次方,2的35次方就是32G了。
所以啥,對象已經按8字節邊界對齊這個因素還是很關鍵的。
JVM - 剖析Java對象頭Object Header之指針壓縮
同時在64位平台的HotSpot中使用32位指針(實際存儲用64位),內存使用會多出1.5倍左右,使用較大指針在主內存和緩存之間移動數據,占用較大寬帶。
- 當堆內存小於4G時,不需要啟用指針壓縮,jvm會直接去除高32位地址,即使用低虛擬地址空間
- 當堆內存大於32G時,壓縮指針會失效,會強制使用64位(即8字節)來對java對象尋址, 那這樣的話內存占用較大,GC壓力等等