来聊聊大厂面试常问的指针压缩

写在文章开头

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

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

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

你好,我叫sharkchili,目前还是在一线奋斗的Java开发,经历过很多有意思的项目,也写过很多有意思的文章,是CSDN Java领域的博客专家,也是Java Guide的维护者之一,非常欢迎你关注我的公众号:写代码的SharkChili,这里面会有笔者精心挑选的并发、JVM、MySQL数据库专栏,也有笔者日常分享的硬核技术小文。

指针压缩详解

Java对象内存布局

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

  1. 对象头
  2. 实例数据
  3. 对齐填充

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

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

bash 复制代码
java -xx:+PrintCommandLineFlags -version

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

bash 复制代码
-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的内存布局,对应示例代码如下:

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

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

bash 复制代码
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位。

bash 复制代码
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的数据,可得:

bash 复制代码
3567/8=445.875

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

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

指针压缩的局限性

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

小结

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

我是sharkchiliCSDN Java 领域博客专家开源项目---JavaGuide contributor ,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili ,同时我的公众号也有我精心整理的并发编程JVMMySQL数据库个人专栏导航。

参考

聊一聊JAVA指针压缩的实现原理(图文并茂,让你秒懂):blog.csdn.net/liujianyang...

JVM的指针压缩:zhuanlan.zhihu.com/p/491684586

相关推荐
XovH3 分钟前
Django 从 0 到 1 打造完整电商平台:收货地址管理
后端
Postkarte不想说话21 分钟前
Jupyter Lab安装
后端
fliter24 分钟前
在 Async Rust 中实现请求合并(Request Coalescing)
后端
王立志_LEO24 分钟前
Gunicorn 启动django服务
后端
fliter25 分钟前
一个让我调试一周的 Rust match 陷阱
后端
一只大袋鼠36 分钟前
SpringBoot 初学阶段知识点汇总(一)
spring boot·笔记·后端
Rust研习社38 分钟前
Rust 官方拟定 LLM 政策,防止 LLM 污染开源社区?
开发语言·后端·ai·rust·开源
无风听海1 小时前
ASP.NET Core Minimal API 深度解析
后端·asp.net
IT_陈寒1 小时前
Java的finally块竟然不是你想的那个finally!
前端·人工智能·后端