java8 -XX:+ UseCompressedOops -XX:ObjectAlignmentInBytes = 16

Eug*_*ene 7 java-8

所以,我正在尝试运行一些简单的代码,jdk-8,通过jol输出

    System.out.println(VMSupport.vmDetails());
    Integer i = new Integer(23);
    System.out.println(ClassLayout.parseInstance(i)
            .toPrintable());
Run Code Online (Sandbox Code Playgroud)

第一次尝试是在禁用压缩oops的情况下运行它,并在64位JVM上运行压缩klass.

-XX:-UseCompressedOops -XX:-UseCompressedClassPointers
Run Code Online (Sandbox Code Playgroud)

输出,非常期望是:

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
Run Code Online (Sandbox Code Playgroud)

这是有道理的:8字节klass字+8字节标记字+ 4字节用于实际值,4字用于填充(对齐8字节)= 24字节.

第二次尝试使用压缩oops运行它也在64位JVM上启用了压缩klass.

同样,输出几乎是可以理解的:

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).
Run Code Online (Sandbox Code Playgroud)

4字节压缩oop(klass字)+ 8字节标记字+ 4字节值+无空间损失= 16字节.

对我来说没有意义的是这个用例:

 -XX:+UseCompressedOops -XX:+UseCompressedClassPointers -XX:ObjectAlignmentInBytes=16
Run Code Online (Sandbox Code Playgroud)

输出是这样的:

 Running 64-bit HotSpot VM.
 Using compressed oop with 4-bit shift.
 Using compressed klass with 0x0000001000000000 base address and 0-bit shift.
Run Code Online (Sandbox Code Playgroud)

我真的希望两者都是"4位移位".他们为什么不呢?

编辑 第二个例子运行:

 XX:+UseCompressedOops -XX:+UseCompressedClassPointers
Run Code Online (Sandbox Code Playgroud)

第三个:

 -XX:+UseCompressedOops -XX:+UseCompressedClassPointers -XX:ObjectAlignmentInBytes=16
Run Code Online (Sandbox Code Playgroud)

Ale*_*lev 7

在查看OpenJDK代码时,很容易弄清楚这些问题的答案.

例如,grep用于"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);
  }
}
Run Code Online (Sandbox Code Playgroud)

好的,有趣的是,有"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)                                                   \
Run Code Online (Sandbox Code Playgroud)

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) {
Run Code Online (Sandbox Code Playgroud)

因此,压缩类在Java堆外部的较小类空间中分配,这不需要移位 - 即使3千兆字节小到足以仅使用最低32位.

  • AFAIU,没有`UseCompressedClassPointers`,类镜像仍然在堆上分配,对它们的引用被压缩为对其他对象的引用.这就是为什么他们需要像其他普通Java对象那样的3位移位. (2认同)

Vla*_* G. 5

我将尝试对 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);
}
Run Code Online (Sandbox Code Playgroud)

正如我们所见,类移位可以是 0(或基本不移位)或 3 位,因为 LogKlassAlignmentInBytes 是 globalDefinitions.hpp 中定义的常量:

const int LogKlassAlignmentInBytes = 3;
Run Code Online (Sandbox Code Playgroud)

所以,你的问题的答案:

我真的很期待两者都是“4位移位”。为什么他们不是?

是 ObjectAlignmentInBytes 对始终为 8 字节的元空间中的压缩类指针对齐没有任何影响。

当然,这个结论并没有回答这个问题:

“为什么当使用 -XX:ObjectAlignmentInBytes=16 和 -XX:+UseCompressedClassPointers 时,窄类移位变为零?此外,如果堆是 4GBytes 或更多,如果不移位,JVM 如何使用 32 位引用引用类空间?”

我们已经知道类空间是在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
Run Code Online (Sandbox Code Playgroud)

请注意,堆从虚拟空间中的地址 0x00000006c0000000 开始,大小为 4GBytes。让我们从堆开始的地方跳过 4GB,我们就在类空间开始的地方。

0x00000006c0000000 + 0x0000000100000000 = 0x00000007c0000000
Run Code Online (Sandbox Code Playgroud)

类空间大小是 1Gbyte,所以让我们再跳 1Gbyte:

0x00000007c0000000 + 0x0000000040000000 = 0x0000000800000000
Run Code Online (Sandbox Code Playgroud)

我们的数据刚好低于 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
Run Code Online (Sandbox Code Playgroud)

这次我们可以观察到堆地址不同了,但是我们还是尝试相同的步骤:

0x0000000f00000000 + 0x0000000100000000 = 0x0000001000000000
Run Code Online (Sandbox Code Playgroud)

这次堆空间刚好在 64GBytes 虚拟空间边界以下结束,类空间分配在 64Gbyte 边界以上。既然类空间只能使用3位移位,那么JVM如何引用位于64Gbyte以上的类空间呢?关键是:

Narrow klass base: 0x0000001000000000
Run Code Online (Sandbox Code Playgroud)

JVM 仍然使用 32 位压缩指针作为类空间,但是在编码和解码这些指针时,它总是将 0x0000001000000000 基数添加到压缩引用而不是使用移位。请注意,只要引用的内存块低于 4GB(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
Run Code Online (Sandbox Code Playgroud)

在这个测试中,我们仍然保持 -XX:ObjectAlignmentInBytes=16,但也要求 JVM 使用 -XX:HeapBaseMinAddress=8g JVM 参数在虚拟地址空间中的第 8 个 GByte 处分配堆。类空间将从虚拟地址空间的第 12 个 GByte 开始,3 位移位足以引用它。

希望这些测试及其结果回答了这个问题:

“为什么当使用 -XX:ObjectAlignmentInBytes=16 和 -XX:+UseCompressedClassPointers 时,窄类移位变为零?此外,如果堆是 4GBytes 或更多,如果不移位,JVM 如何使用 32 位引用引用类空间?”