所以,我试图运行一些简单的代码,jdk-8,通过jol输出
RootName
第一次尝试是在禁用压缩oops的情况下运行它,并在64位JVM上运行压缩klass。
System.out.println(VMSupport.vmDetails());
Integer i = new Integer(23);
System.out.println(ClassLayout.parseInstance(i)
.toPrintable());
输出,非常期待:
-XX:-UseCompressedOops -XX:-UseCompressedClassPointers
这是有道理的:8字节klass字+8字节标记字+ 4字节用于实际值,4字用于填充(对齐8字节)= 24字节。
第二次尝试使用压缩oops运行它也在64位JVM上启用了压缩klass。
同样,输出几乎可以理解:
Running 64-bit HotSpot VM.
Objects are 8 bytes aligned.
java.lang.Integer object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 48 33 36 97 (01001000 00110011 00110110 10010111) (-1758055608)
12 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
16 4 int Integer.value 23
20 4 (loss due to the next object alignment)
Instance size: 24 bytes (reported by Instrumentation API)
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
4字节压缩oop(klass字)+ 8字节标记字+ 4字节值+无空间损失= 16字节。
对我来说没有意义的是这个用例:
Running 64-bit HotSpot VM.
Using compressed oop with 3-bit shift.
Using compressed klass with 3-bit shift.
Objects are 8 bytes aligned.
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) f9 33 01 f8 (11111001 00110011 00000001 11111000) (-134138887)
12 4 int Dummy.i 42
Instance size: 16 bytes (reported by Instrumentation API).
输出是这样的:
-XX:+UseCompressedOops -XX:+UseCompressedClassPointers -XX:ObjectAlignmentInBytes=16
我真的希望两者都是“4位移位”。为什么他们不是?
修改 第二个示例运行于:
Running 64-bit HotSpot VM.
Using compressed oop with 4-bit shift.
Using compressed klass with 0x0000001000000000 base address and 0-bit shift.
第三个:
XX:+UseCompressedOops -XX:+UseCompressedClassPointers
答案 0 :(得分:7)
在查看OpenJDK代码时,很容易弄清楚这些问题的答案。
例如,grep for“UseCompressedClassPointers”,这将转到arguments.cpp:
// Check the CompressedClassSpaceSize to make sure we use compressed klass ptrs.
if (UseCompressedClassPointers) {
if (CompressedClassSpaceSize > KlassEncodingMetaspaceMax) {
warning("CompressedClassSpaceSize is too large for UseCompressedClassPointers");
FLAG_SET_DEFAULT(UseCompressedClassPointers, false);
}
}
好的,有趣的是,有“CompressedClassSpaceSize”? Grep的定义,它在globals.hpp:
product(size_t, CompressedClassSpaceSize, 1*G, \
"Maximum size of class area in Metaspace when compressed " \
"class pointers are used") \
range(1*M, 3*G) \
Aha,所以类区域位于Metaspace中,占用的空间介于1 Mb和3 Gb之间。让我们来看看“CompressedClassSpaceSize”用法,因为这将把我们带到处理它的实际代码,比如metaspace.cpp:
// For UseCompressedClassPointers the class space is reserved above
// the top of the Java heap. The argument passed in is at the base of
// the compressed space.
void Metaspace::initialize_class_space(ReservedSpace rs) {
因此,压缩类被分配在Java堆外部的较小类空间中,这不需要移位 - 即使3千兆字节小到足以仅使用最低32位。
答案 1 :(得分:3)
我将尝试对Alexey提供的答案进行一点延伸,因为有些事情可能并不明显。
根据Alexey的建议,如果我们在OpenJDK的源代码中搜索分配压缩klass位移值的位置,我们将在metaspace.cpp中找到以下代码:
void Metaspace::set_narrow_klass_base_and_shift(address metaspace_base, address cds_base) {
// some code removed
if ((uint64_t)(higher_address - lower_base) <= UnscaledClassSpaceMax) {
Universe::set_narrow_klass_shift(0);
} else {
assert(!UseSharedSpaces, "Cannot shift with UseSharedSpaces");
Universe::set_narrow_klass_shift(LogKlassAlignmentInBytes);
}
正如我们所看到的,类移位可以是0(或基本上没有移位)或3位,因为LogKlassAlignmentInBytes是globalDefinitions.hpp中定义的常量:
const int LogKlassAlignmentInBytes = 3;
所以,你的回答是:
我真的希望两者都是“4位移位”。为什么他们不是?
是ObjectAlignmentInBytes对元空间中的压缩类指针对齐没有任何影响,它始终是8bytes。
当然,这个结论并没有回答这个问题:
“为什么在使用-XX:ObjectAlignmentInBytes = 16和-XX:+ UseCompressedClassPointers时,狭窄的klass移位变为零?另外,如果没有移动,JVM如何使用32位引用引用类空间,如果堆是4GB还是更多?“
我们已经知道类空间是在java堆之上分配的,并且可以达到3G的大小。考虑到这一点,让我们做一些测试。 -XX:+ UseCompressedOops -XX:默认情况下启用UseCompressedClassPointers,因此我们可以简化这些内容。
测试1:默认值 - 8个字节对齐
$ java -XX:ObjectAlignmentInBytes=8 -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompressedOopsMode -version
heap address: 0x00000006c0000000, size: 4096 MB, zero based Compressed Oops
Narrow klass base: 0x0000000000000000, Narrow klass shift: 3
Compressed class space size: 1073741824 Address: 0x00000007c0000000 Req Addr: 0x00000007c0000000
请注意,堆从虚拟空间中的地址0x00000006c0000000开始,大小为4GBytes。让我们从堆开始的地方跳过4Gbytes,然后我们就到了类空间开始的地方。
0x00000006c0000000 + 0x0000000100000000 = 0x00000007c0000000
类空间大小是1Gbyte,所以让我们跳过另一个1Gbyte:
0x00000007c0000000 + 0x0000000040000000 = 0x0000000800000000
我们降到32Gbytes以下。通过3位类空间移位,JVM能够引用整个类空间,尽管它处于极限(有意)。
测试2:16字节对齐
java -XX:ObjectAlignmentInBytes=16 -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompressedOopsMode -version
heap address: 0x0000000f00000000, size: 4096 MB, zero based Compressed Oops
Narrow klass base: 0x0000001000000000, Narrow klass shift: 0
Compressed class space size: 1073741824 Address: 0x0000001000000000 Req Addr: 0x0000001000000000
这次我们可以观察到堆地址不同,但让我们尝试相同的步骤:
0x0000000f00000000 + 0x0000000100000000 = 0x0000001000000000
此时堆周围的空间刚好低于64GBytes虚拟空间边界,而类空间分配在64Gbyte边界之上。由于类空间只能使用3位移位,JVM如何引用64Gbyte以上的类空间?关键是:
Narrow klass base: 0x0000001000000000
JVM仍然使用32位压缩指针作为类空间,但是当对它们进行编码和解码时,它总是将0x0000001000000000 base添加到压缩引用而不是使用shift。请注意,只要引用的内存块低于4Gbytes(32位引用的限制),此方法就可以工作。考虑到类空间最大可以达到3G字节,我们可以轻松地在限制范围内。
3:16字节对齐,引脚堆基于8g
$ java -XX:ObjectAlignmentInBytes=16 -XX:HeapBaseMinAddress=8g -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompressedOopsMode -version
heap address: 0x0000000200000000, size: 4096 MB, zero based Compressed Oops
Narrow klass base: 0x0000000000000000, Narrow klass shift: 3
Compressed class space size: 1073741824 Address: 0x0000000300000000 Req Addr: 0x0000000300000000
在此测试中,我们仍然保留-XX:ObjectAlignmentInBytes = 16,但还要求JVM使用-XX:HeapBaseMinAddress = 8g JVM参数在虚拟地址空间的第8 GByte分配堆。类空间将从虚拟地址空间的第12 GByte开始,3位移位足以引用它。
希望这些测试及其结果能够回答这个问题:
“为什么在使用-XX:ObjectAlignmentInBytes = 16和-XX:+ UseCompressedClassPointers时,狭窄的klass移位变为零?此外,如果堆是4GBytes或者堆,那么JVM如何引用具有32位引用的类空间?更?“