在64位平臺上,hotspot使用稱為壓縮對象指針(“CompressedOops”)和壓縮類指針的優(yōu)化技術。兩者都是同一事物的變體。
壓縮指針是一種引用數據(Java堆中的對象或元空間中的類元數據)的方法,即使在64位平臺上也使用32位引用。
這有許多優(yōu)點,例如指針大小更小,從而減少內存占用和更好地利用緩存,并且在某些平臺上可以使用更多的寄存器。
Note: A good explanation of Compressed Object Pointers can be found here: JVM Anatomy Quark #23: Compressed References.
Also, a similar motivation drives the Linux x32 abi.
因為最終一個人需要一個64位的地址來訪問那個東西,那個32位的“指針”實際上是一個偏移量——可能是位移位——進入一個具有已知公共基的區(qū)域。
關于Metaspace,我們不關心壓縮的oop,但必須處理壓縮類指針:
每個Java對象的頭中都有一個對Metaspace中Java堆之外的本機結構的引用:Class結構。

使用壓縮類指針時,該引用是32位值。為了找到該結構的真正64位地址,我們向其添加一個已知的公共基,并可能將值左移三位:

該技術對如何分配這些Klass結構設置了技術限制:
Klass結構的每個可能的位置必須在4G(非移位模式)| 32G(移位模式)的范圍內,以從公共基址1的32位偏移量可到達。
這兩個限制意味著我們需要將元空間分配為一個連續(xù)的區(qū)域。
當通過malloc(3)或mmap(3)這樣的系統(tǒng)API從系統(tǒng)分配內存時,地址由系統(tǒng)選擇,并且可以是適合類型范圍的任何值。因此,在64位平臺上,當然不能保證后續(xù)分配會在范圍限制內產生地址。E、 g.一個mmap(3)調用可以映射到0x0000000700000000,一個映射到0x0000000f0000000。
因此,我們必須使用一個mmap()調用來建立Klass對象的區(qū)域。因此,我們需要預先知道這個區(qū)域的大小,它不能大于32G,也永遠不能可靠地擴展,因為超出其末端的地址范圍可能已經被占用。
這些限制是嚴厲的。它們也只是真正需要用于Klass結構,而不是用于其他類元數據:目前只有Klass實例被壓縮引用處理。因此,可以將其他64位指針放在任何位置。
因此決定將元空間分成兩部分:“非類部分”和“類部分”:
- 等級部分,包括Class結構,必須分配為一個不大于32G的連續(xù)區(qū)域。
- 包含其他所有內容的非類部分則沒有。
Terminology: The class part is called “Compressed Class Space” even though that is a bit of a misnomer since the Klass structures themselves are not compressed but the pointers to them.
壓縮類空間的大小由-XX:CompressedClassSpaceSize決定。因為我們需要預先知道類空間的大小,所以該參數不能為空。如果省略,則默認為1GB。
更令人困惑的是,hotspot人為地將classspacesize壓縮到3G的最大值——我真的不知道為什么。因此,除了32G的技術限制之外,我們還人為地設置了3G的限制。
另外請注意,我們一直在談論虛擬尺寸,而不是漫畫尺寸。這種記憶只有在需要的時候才會提交。非常簡化,虛擬大小在大多數現代操作系統(tǒng)上幾乎不需要任何成本,它只是一個addres空間預留。
由于Klass結構的平均大小為1K,一個默認大小為1G的壓縮類空間將能夠容納大約一百萬個Klass結構(參見調整元空間大小)。這是我們可以加載的類數量的唯一實際限制。
還請注意,當我們不使用CompressedOops運行時,compressedClasspointer將被禁用。如果我們通過-XX:-CompressedOops手動關閉CompressedOops,或者Java堆大于或等于32G,就會發(fā)生這種情況。
往期參考:http://javakk.com/160.html
Implementation
為了重用現有的元空間實現,采用了一個技巧:
全局結構VirtualSpaceList和ChunkManager都是重復的,現在存在于兩個變體中,“類空間”變量和“非類空間”變量。
但是由于類空間需要一個連續(xù)的地址范圍,我們不能真正使用映射區(qū)域鏈;因此類空間列表退化了:它只包含一個節(jié)點,不能增長。與非類列表中的同類節(jié)點相比,這個節(jié)點是巨大的。這個節(jié)點就是壓縮的類空間。


ClassLoaderMetaspace——每個類裝入器結構都包含使用這個類裝入器的塊——現在需要兩個鏈接的塊列表,一個用于保存非類塊,另一個用于類塊。這也意味著我們將當前節(jié)點的“空閑”部分加倍,因為現在我們有兩個節(jié)點。
開關:UseCompressedClassPointers、UseCompressedOops
-XX:+UseCompressedOops啟用壓縮對象指針。-XX:+UseCompressedClassPointers啟用壓縮類指針。
默認情況下,兩者都處于打開狀態(tài),但可以手動關閉。
如果壓縮類指針被關閉,我們將沒有壓縮的類空間,并且-XX:CompressedClassSpaceSize開關將被忽略。
-XX:+UseCompressedClassPointers需要-XX:+useCompressedDoops,但反之亦然:可以在沒有壓縮類指針的情況下運行壓縮oops。這可能有助于在一些病態(tài)的角落案例中減少元空間內存占用。一般來說,建議不要使用這些開關。
注意,壓縮對象指針需要Java堆<32G。因此,如果Java堆>=32G,壓縮oop將被關閉,這也將關閉壓縮類指針。
文章來源:http://javakk.com/405.html
也歡迎大家關注我的公眾號【Java老K】獲取更多干貨