JVM內存不要超過32G


不要超過32G


事實上jvm在內存小於32G的時候會采用一個內存對象指針壓縮技術。
 
在java中,所有的對象都分配在堆上,然后有一個指針引用它。指向這些對象的指針大小通常是CPU的字長的大小,不是32bit就是64bit,這取決於你的處理器,指針指向了你的值的精確位置。
 
對於32位系統,你的內存最大可使用4G。對於64系統可以使用更大的內存。但是64位的指針意味着更大的浪費,因為你的指針本身大了。浪費內存不算,更糟糕的是,更大的指針在主內存和緩存器(例如LLC, L1等)之間移動數據的時候,會占用更多的帶寬。
 
Java 使用一個叫內存指針壓縮的技術來解決這個問題。它的指針不再表示對象在內存中的精確位置,而是表示偏移量。這意味着32位的指針可以引用40億個對象,而不是40億個字節。最終,也就是說堆內存長到32G的物理內存,也可以用32bit的指針表示。
 
一旦你越過那個神奇的30-32G的邊界,指針就會切回普通對象的指針,每個對象的指針都變長了,就會使用更多的CPU內存帶寬,也就是說你實際上失去了更多的內存。事實上當內存到達40-50GB的時候,有效內存才相當於使用內存對象指針壓縮技術時候的32G內存。
 
這段描述的意思就是說:即便你有足夠的內存,也盡量不要超過32G,因為它浪費了內存,降低了CPU的性能,還要讓GC應對大內存。

JVM內存不要超過32G_zsj777的專欄-CSDN博客_jvm內存超過32g

1、將Java Heap Size設置的大於32G會對性能有什么影響?

開門見山的說,結果有幾點(這幾點其實也是內部關聯):

  • 觸發JVM的臨界值,優化策略Compressed OOPS失效(之前Heap Size在[4G~32G]區間內采用此優化)

  • 由於優化策略失效,同時堆內存>32G,所以JVM被迫使用8字節(64位)來對Java對象尋址(之前4字節(32位)就夠了)

  • 通常64位JVM消耗的內存會比32位的大1.5倍,這是因為對象指針在64位架構下,長度會翻倍(事實上當內存到達40-50GB的時候,有效內存才相當於使用Compressed OOPS技術時候的32G內存)

  • 更大的指針在主內存和緩存器(例如LLC, L1等)之間移動數據的時候,會占用更多的帶寬

  • 讓JVM的GC面臨更大壓力的指針對象(在實際應用中構建大於12-16G的堆時,若無很好的性能調優與測評,你很容易就會引起一個耗時數分鍾的完全GC)

1.1 JVM的OOPS

OOP = “ordinary object pointer” 普通對象指針

啟用CompressOops后,會壓縮的對象:

  • 每個class的屬性指針(靜態成員變量)

  • 每個對象的屬性指針

  • 普通對象數組的每個元素指針

1.2 JVM的優化策略Compressed OOPS

從JDK 1.6 update14開始,64 bit JVM正式支持了 -XX:+UseCompressedOops ,這個可以壓縮指針,起到節約內存占用的新參數。

Compressed OOPS,即大霧的對象壓縮技術,壓縮引用到32位,以降低堆的占用空間。其偽代碼原理就不貼了,

在堆大小在[4G~32G]的時候,這項技術會被觸發,在JVM執行時加入編/解碼指令,即

JVM在將對象存入堆時編碼,在堆中讀取對象時解碼

內存地址確定公式類似於

1
<narrow-oop-base(64bits)> +(<narrow-oop(32bits)><< 3) +<field-offset>

Zero Based Compressed OOPS(零基壓縮優化)則進一步將基地址置為0(並不一定是內存空間地址為0,只是JVM相對的邏輯地址為0,如可用CPU的寄存器相對尋址) 這樣轉換公式變為:

1
(<narrow-oop << 3) +<field-offset>

從而進一步提高了壓解壓效率。

 

使用Zero Based Compressed OOPS后,它的指針不再表示對象在內存中的精確位置,而是表示偏移量。這意味着32位的指針可以引用40億個對象,而不是40億個字節。

1.3 Zero Based Compressed OOPS的多種策略

它可以針對不同的堆大小使用多種策略,具體可以 ps + grep查看:

  • 堆小於4G,無需編/解碼操作,JVM會使用低虛擬地址空間(low virutal address space,64位下模擬32位)

  • 小於32G而大於4G,使用Zero Based Compressed OOPS

  • 大於32G,不使用Compressed OOPS

2、結論

  • Compressed OOPS,可以讓跑在64位平台下的JVM,不需要因為更寬的尋址,而付出Heap容量損失的代價

  • 它的實現方式是在機器碼中植入壓縮與解壓指令,可能會給JVM增加額外的開銷

 為什么JVM開啟指針壓縮后支持的最大堆內存是32G? - 知乎

為什么JVM開啟指針壓縮后支持的最大堆內存是32G?

 

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壓力等等


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM