Sun的HotSpot VM從JDK5開始會根據運行環境來自動設定VM的一些參數(ergonomics)。其中大家最熟悉的可能是它會自動選擇client與server模式、堆的初始和最大大小等。事實上ergonomics會設置非常多的內部參數,包括自動選擇GC算法、并行GC的線程數、GC的工作區分塊大小、對象晉升閾值等等。
Ergonomics相關的邏輯大都在hotspot/src/share/vm/runtime/arguments.cpp中,值得留意的是使用了FLAG_SET_ERGO()的地方。
于是我們可以留意一下幾個版本的HotSpot對UseCompressedOops參數的處理的差異:
HotSpot 16:
C++代碼
#ifdef _LP64 // Check that UseCompressedOops can be set with
the max heap size allocated // by ergonomics. if (MaxHeapSize <= max_heap_for_compressed_oops()) { if (FLAG_IS_DEFAULT(UseCompressedOops)) { // Turn off until bug is fixed. // the following line to return it to default status. // FLAG_SET_ERGO(bool, UseCompressedOops, true); } // ... } #endif // _LP64
HotSpot 17:
C++代碼
#ifndef ZERO #ifdef _LP64 // Check that UseCompressedOops can be set with
the max heap size allocated // by ergonomics. if (MaxHeapSize <= max_heap_for_compressed_oops()) { #ifndef COMPILER1 if (FLAG_IS_DEFAULT(UseCompressedOops) && !UseG1GC) { // Disable Compressed Oops by default. Uncomment
next line to enable it. // FLAG_SET_ERGO(bool, UseCompressedOops, true); } } #endif // ... #endif // _LP64 #endif // !ZERO
HotSpot 19 / HotSpot 20:
C++代碼
#ifndef ZERO #ifdef _LP64 // Check that UseCompressedOops can be set with
the max heap size allocated // by ergonomics. if (MaxHeapSize <= max_heap_for_compressed_oops()) { #ifndef COMPILER1 if (FLAG_IS_DEFAULT(UseCompressedOops) && !UseG1GC) { FLAG_SET_ERGO(bool, UseCompressedOops, true); } #endif } // ... #endif // _LP64 #endif // !ZERO
(注:HotSpot VM的版本號與JDK的版本號之間的關系,請參考另一篇筆記:Sun/Oracle JDK、OpenJDK、HotSpot VM版本之間的對應關系)
可以看到,UseCompressedOops參數從HotSpot 19開始終于開始受ergonomics控制,會在下述條件滿足的時候默認開啟管道磁力泵:
1、是64位系統(#ifdef _LP64)并且不是client VM(#ifndef COMPILER1);
2、Java堆的最大大小不大于一個閾值(MaxHeapSize <= max_heap_for_compressed_oops());
3、沒有通過。hotspotrc或命令行參數手動設定過UseCompressedOops參數的值;
4、沒有使用Garbage-First (G1) GC.
第1、3、4點都很直觀,于是第2點就是個關鍵點了:閾值是多大?
還是看回代碼,HotSpot 20:
C++代碼
void set_object_alignment() { // Object alignment. assert(is_power_of_2(ObjectAlignmentInBytes), "ObjectAlignmentInBytes must be power of 2"); MinObjAlignmentInBytes = ObjectAlignmentInBytes; assert(MinObjAlignmentInBytes >= HeapWordsPerLong * HeapWordSize,
"ObjectAlignmentInBytes value is too small"); MinObjAlignment = MinObjAlignmentInBytes / HeapWordSize; assert(MinObjAlignmentInBytes == MinObjAlignment * HeapWordSize,
"ObjectAlignmentInBytes value is incorrect"); MinObjAlignmentInBytesMask = MinObjAlignmentInBytes - 1; LogMinObjAlignmentInBytes = exact_log2(ObjectAlignmentInBytes); LogMinObjAlignment = LogMinObjAlignmentInBytes - LogHeapWordSize; // Oop encoding heap max OopEncodingHeapMax = (uint64_t(max_juint) + 1) << LogMinObjAlignmentInBytes; } inline uintx max_heap_for_compressed_oops() { // Avoid sign flip. if (OopEncodingHeapMax < MaxPermSize + os::vm_page_size()) { return 0; } LP64_ONLY(return OopEncodingHeapMax - MaxPermSize - os::vm_page_size()); NOT_LP64(ShouldNotReachHere(); return 0); }
(注:其中 (uint64_t(max_juint) + 1) 的值也被稱為NarrowOopHeapMax,也就是2的32次方,0x100000000;
ObjectAlignmentInBytes在64位HotSpot上默認為8;
HeapWord在globalDefinitions.hpp里定義,大小跟一個char*一樣;
HeapWordSize在同一個文件里定義,等于sizeof(HeapWord),在64位系統上值為8;
LogHeapWordSize也在同一文件里,在64位系統上定義為3)
跟蹤一下里面幾個參數的計算,在64位HotSpot上有,
C++代碼
- ObjectAlignmentInBytes = 8
- MinObjAlignmentInBytes = 8
- HeapWordSize = 8
- MinObjAlignment = 1
- MinObjAlignmentInBytesMask = 0x0111
- LogMinObjAlignmentInBytes = 3
- LogHeapWordSize = 3 // _LP64
- LogMinObjAlignment = 0
OopEncodingHeapMax = 0x800000000 // 32GB
于是,前面提到的第2個條件在64位HotSpot VM上默認是:
C++代碼
MaxHeapSize + MaxPermSize + os::vm_page_size() <= 32GB
os::vm_page_size()是操作系統的虛擬內存的分頁大小,在Linux上等于sysconf(_SC_PAGESIZE)的值;在x86_64上的Linux默認分頁大小為4KB.
MaxHeapSize的值基本上等于-Xmx參數設置的值(會根據分頁大小、對齊等因素做調整)。
MaxPermSize就是perm gen設置的最大大小。
這下可以確認,在我現在用的環境里,當包括perm gen在內的GC堆大小在32GB - 4KB以下的時候,使用64位的JDK 6 update 23或更高版本就會自動開啟UseCompressedOops功能