JAVA面试题分享四百六十六:大厂面试常问的指针压缩

目录

写在文章开头

指针压缩详解

Java对象内存布局

为什么需要指针压缩

指针压缩引发的寻址问题和解决方案

指针压缩的局限性

小结


写在文章开头

近期和朋友聊天时聊到一些比较有趣的面试题?

1.指针压缩了解过吗?它解决什么问题? 2. 为什么设置JVM内存时不建议设置超过32G?它和指针压缩有什么关系?

针对上述两道面试题,笔者就以此文展开深入讲解:

图片

 

指针压缩详解

Java对象内存布局

我们都知道Hotspot虚拟机在中的java对象的整体布局为:

  1. 对象头

  2. 实例数据

  3. 对齐填充

其中对象头由Mark world、类型指针及数组长度组成。这其中类型指针就是本文的主角,它指向了当前实例元数据的指针,通过这个指针,虚拟就可以确认当前对象是属于哪一个类的实例。 我们以64位操作系统为例,在未开启指针压缩的情况下,类型指针占用8字节,一旦开启指针压缩,类型指针就会由原来的8字节变为4字节。

图片

对此,这里我们不妨键入下面这条命令看看,JDK8版本对于指针压缩的设定:

java -xx:+PrintCommandLineFlags -version

从输出结果来看,JDK8在默认情况下已经开启了普通对象压缩和类型指针压缩:

-XX:InitialHeapSize=262499904 -XX:MaxHeapSize=4199998464 -XX:+PrintCommandLineFlags 
-XX:+UseCompressedClassPointers # 类型指针压缩
-XX:+UseCompressedOops # 普通对象压缩
-XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_251"
Java(TM) SE Runtime Environment (build 1.8.0_251-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.251-b08, mixed mode)

对此,我们使用JOL查看一下Object的内存布局,对应示例代码如下:

public static void main(String[] args) {
        // 打印对象大小
        System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());
    }

从输出结果来看,默认情况下,JDK8版本的类型指针仅仅在OFFSET为8的位置占用了4个字节的内存空间:

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
 # mark world
      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)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
      # 对齐填充
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

而关闭指针压缩(-XX:-UseCompressedClassPointers)后,类型指针占用字节数为8位。

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
  # mark world
      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)                           00 1c 34 1c (00000000 00011100 00110100 00011100) (473177088)
     12     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

为什么需要指针压缩

先来说说第一个原因,默认情况下类型指针为8个字节,相较于指针压缩后的对象,它无缘无故就比后者多了4个字节,这意味着64位对象需要占用更多的堆空间,无意间加快的GC的发生,导致更频繁的GC

我们都知道CPU在进行工作时都需要从内存中加载数据到缓存行中,所以为了保证有限的缓存行可以缓存尽可能多的数据,通过压缩指针的字节数,减小对象的体积,从而提升CPU缓存的对象数,进而提升CPU缓存命中率,提高程序的执行效率。

指针压缩引发的寻址问题和解决方案

类型指针占用内存空间由8byte改为4byte,就意味原本类型指针可以记录18446T的内存地址(2^64)变为4G(2^32),因为一个指针压缩导致64位操作系统的寻址范围大大减少,很明显这是得不偿失的。

所以设计者们就提出让压缩后的指针1个bit表示8个bit的内存空间,即假设我们的对象类型指针数据为200,这就意味着当前类型指针实际的地址起始地址为200*81600位置。

提出等比记录的概念之后,又一个问题诞生了,假设我们的其中一个对象的地址为3567,因为1个bit表示8bit的数据,可得:

3567/8=445.875

即说明当前对象首地址在445bit446bit之间的一个位置。

很明显无论如何计算都无法精确定位到这个对象的位置,所以为了解决等比记录后无法精确定位对象地址的问题,设计者们就用到了8位对齐的做法。 由上可知,我们的压缩后的指针记录地址都是8倍的地址值,这也就意味着只要我们的地址能够被8整除,那么元数据地址就可以被精准定位,所以上述3567字节进行8位对齐后大小为3568,这样以来,压缩指针只需定位从446bit(3568/8)开始即可精确记录对象地址信息了。

指针压缩的局限性

由上可知,指针压缩后最大寻址范围是32G,所以一旦我们设置堆内存超过32G(-Xms32g -Xmx32g),那么指针压缩就会自动时效。这也是为什么很多Java第三方工具都建议单个运行实例的内存设置不要超过32GB的根本原因。

小结

相信通过本文对于java对象内存布局的剖析打印以及图文梳理,读者可以非常清晰了解了指针压缩的作用、使用场景还有局限性。如果你觉得文章对你有帮助也非常希望你帮忙点赞、转发。

  • 16
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

之乎者也·

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值