Java 8 java8-XX:+;UseCompressedOops-XX:ObjectAlignmentInBytes=16

Java 8 java8-XX:+;UseCompressedOops-XX:ObjectAlignmentInBytes=16,java-8,Java 8,所以,我尝试运行一些简单的代码,jdk-8,通过jol输出 System.out.println(VMSupport.vmDetails()); Integer i = new Integer(23); System.out.println(ClassLayout.parseInstance(i) .toPrintable()); 第一次尝试是在64位JVM上禁用压缩oops和压缩klass的情况下运行它。 -XX:-UseCompressedO

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

    System.out.println(VMSupport.vmDetails());
    Integer i = new Integer(23);
    System.out.println(ClassLayout.parseInstance(i)
            .toPrintable());
第一次尝试是在64位JVM上禁用压缩oops和压缩klass的情况下运行它。

-XX:-UseCompressedOops -XX:-UseCompressedClassPointers
预期的结果是:

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
这是有道理的:8字节klass word+8字节mark word+4字节表示实际值,4字节表示填充(在8字节上对齐)=24字节

第二次尝试在64位JVM上使用支持压缩oops的压缩klass运行它。

-XX:-UseCompressedOops -XX:-UseCompressedClassPointers
同样,输出结果非常容易理解:

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).
4字节压缩oop(klass字)+8字节标记字+4字节表示值+无空间丢失=16字节

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

 -XX:+UseCompressedOops -XX:+UseCompressedClassPointers -XX:ObjectAlignmentInBytes=16
输出如下:

 Running 64-bit HotSpot VM.
 Using compressed oop with 4-bit shift.
 Using compressed klass with 0x0000001000000000 base address and 0-bit shift.
我真的希望两者都是“4位移位”。为什么不是呢

编辑 第二个示例在以下情况下运行:

 XX:+UseCompressedOops -XX:+UseCompressedClassPointers
第三个是:

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

在研究OpenJDK代码时,这些问题的答案很容易找到

例如,grep表示“UseCompressedClassPointers”,这将使您能够:

好吧,有趣的是,有“压缩类空间大小”?Grep的定义是:

啊哈,所以类区域在元空间中,它需要1 Mb到3 Gb的空间。让我们回顾一下“CompressedClassSpaceSize”的用法,因为这将使我们了解处理它的实际代码,比如:


因此,压缩类被分配到Java堆之外的一个较小的类空间中,而Java堆不需要移位——即使是3 GB的大小也足以只使用最低的32位。

我将尝试对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);
}
那么,你的问题的答案是:

我真的希望两者都是“4位移位”。为什么不是呢

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

当然,这个结论并不能回答这个问题:

“为什么将-XX:ObjectAlignmentInBytes=16与-XX:+UseCompressedClassPointers一起使用时,klass窄移位变为零?另外,如果堆为4G字节或更多,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
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
请注意,堆从虚拟空间中的地址0x00000006c000000开始,大小为4GBytes。让我们从堆开始的地方跳4G字节,然后在类空间开始的地方着陆

0x00000006c0000000 + 0x0000000100000000 = 0x00000007c0000000
类空间大小为1Gbyte,因此让我们再跳转1Gbyte:

0x00000007c0000000 + 0x0000000040000000 = 0x0000000800000000
我们的着陆速度刚好低于32GB。通过3位类空间的移位,JVM能够引用整个类空间,尽管它(有意地)处于极限

测试2:16字节对齐

$ 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
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
这一次,堆空间在64GB虚拟空间边界以下结束,类空间分配在64GB边界以上。由于类空间只能使用3位移位,JVM如何引用位于64GB以上的类空间?关键是:

Narrow klass base: 0x0000001000000000
JVM仍然使用32位压缩指针作为类空间,但是在编码和解码这些指针时,它总是将0x000000100000000基添加到压缩引用中,而不是使用移位。请注意,只要引用的内存块低于4Gbytes(32位引用的限制),这种方法就可以工作。考虑到该类空间的最大容量为3GB,我们可以轻松地控制在限制范围内

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个GB字节。类空间将从虚拟地址空间中的12GB开始,3位移位足以引用它

希望这些测试及其结果能够回答以下问题:

“为什么将-XX:ObjectAlignmentInBytes=16与-XX:+UseCompressedClassPointers一起使用时,klass窄移位变为零?另外,如果堆为4G字节或更多,JVM如何在不移位的情况下使用32位引用引用类空间?”


这是一个很好的解释,但有一些小事情我想在你的帮助下进一步了解。所以,在元空间中有一个空间(我知道它是堆外的),称为CompressedClassSpace,它的最大大小是3G,32位足以引用这些3G,所以不需要移位,对吗?如果是这样的话,那么为什么在第二个示例中仍然要转换呢?AFAIU,如果没有
UseCompressedClassPointers
,类镜像仍然在堆上分配,并且对它们的引用被压缩为对其他对象的引用。这就是为什么它们需要3位移位,就像其他普通Java对象一样。实际上,如果你看第二个和第三个示例,唯一的区别是-XX:ObjectAlignmentInBytes=16。无论是8还是16,压缩类引用总是处于启用状态,这令人困惑。我不清楚“第二个”示例的命令行标志是什么。请在问题中详细说明它们。在使用和不使用-Djol.tryWithSudo=true运行同一代码段时,注意到一些有趣的事情。因此,不带jo的输出
$ 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