作者 | 哪儿来的moon  责编 | 张文

头图 | CSDN 下载自视觉中国

来源 | 哪儿来的moon(ID:onetraveller_llxz)

面试的时候,要是面试官问了标题那种问题,你知道怎么解答吗?

本文内容将针对以上面试问题做出解答。

对象的创建过程

以下内容基于 HotSpot VM 分代模型。

这张图其实就能完整的说明一个对象的创建过程到底发生了什么。从左上角开始,一步一步来:

  1. 一个对象 new 出来先判断线程栈是否能分配下;

  • 如果能分配下,直接分配在栈中。

  • 如果分配不下则进行第二步。

  • 判断该对象是否足够大;

    • 如果足够大,则直接进入老年代。

    • 如果不够大,则进行第三步。

  • 判断创建对象的线程的 TLAB(本地线程缓冲区)空间是否足够;

    • 如果足够,直接分配在 TLAB 中。

    • 如果不够,则进入 Eden 区中其他空间。然后进行第四步。

  • GC 清除;

    • 如果清除掉了该对象,则直接结束。

    • 如果没有清除掉对象,进行第5步。

  • 此刻对象进入 Survivor 1 区,判断年龄是否足够大;

    • 如果年龄足够大,则直接进入 old 区域。

    • 如果年龄不够大,则进入 Survivor 2 区,然后进入第4步,循环往复。

    通过这张流程图的步骤解析之后,大家应该对一个对象的创建过程有一个相对清晰的思路了。但其实会有很多小细节容易被忽略,这也就是为什么 jvm 会在对象的创建过程中不遗余力的解释,细分多种情况。

    为了让大家更深入地理解,我们再来看看下面几个问题:

    1. 为什么对象会选择先分配在栈中?

      首先,栈是线程私有的,将对象优先分配在栈中,可以通过 pop 直接将对象的所有信息、空间直接清除,当线程消亡的时候也可以直接清理这一块的 TLAB 区域。

    2. 为什么 jvm 会让大对象会直接进入老年代?

      大对象需要连续的空间来存储,如果不存入老年代,对 jvm 来说就是一个负担;倘若没有足够的空间就有可能导致提前触发 gc 来清理空间来安置大对象。

    3. 为什么会选择先进入 TLAB?

      TLAB 是线程本地缓冲区,TLAB 的好处就是防止不同线程创建对象选择同一块儿内存区域而产生竞争,大大降低其竞争概率。

    4. 为什么会有两个 Survivor 区?并且存活且年龄不够大的对象会从一个Survivor 区转到另一个 Survivor 区?

      根据根可达算法,jvm 会寻找到所有正在使用的对象,没有使用的就是垃圾。通常来说,大部分对象都是用完就抛弃的,所以真正在 Survivor 区长时间存活的对象非常少,将这部分对象从一个 Survivor 区转到另一个 Survivor 区后,就可以直接对这个 Survivor 区进行全量的空间回收,效率会很高。


    对象的内存布局

    回到文章标题,Object o = new Object();到底占用多少个字节?这道题的目的其实就是考验看你对对象的内存布局了解的是否清晰。先上图:

    在 java 中对象的内存布局分为两种情况:非数组对象和数组对象,数组对象和非数组对象的区别就是需要额外的空间存储数组的长度length

    对象头

    对象头又分为 MarkWord 和 Class Pointer 两部分。

    • MarkWord:包含一系列的标记位,比如轻量级锁的标记位、偏向锁标记位、gc记录信息等等。在32位系统占4字节,在64位系统中占8字节。

    • ClassPointer:用来指向对象对应的Class对象(其对应的元数据对象)的内存地址。在32位系统占4字节,在64位系统中占8字节。

    • Length:只在数组对象中存在,用来记录数组的长度,占用4字节。

    Interface data

    • Interface data:对象实际数据。对象实际数据包括了对象的所有成员变量,其大小由各个成员变量的大小决定。(这里不包括静态成员变量,因为它是在方法区维护的)

    Padding

    • Padding:Java 对象占用空间是 8 字节对齐的,即所有 Java 对象占用 bytes 数必须是 8 的倍数。这是因为当我们从磁盘中取一个数据时,不会说我想取一个字节就是一个字节,都是按照一块儿一块儿来取的。这一块大小是 8 个字节,所以为了完整,padding 的作用就是补充字节,保证对象是 8 字节的整数倍。

    小贴士:moon 在上文特意标注了 32 位系统和 64 位系统不同区域占用空间大小的区别,是因为对象指针在 64 位 JVM 下的寻址更长,所以相比 32 位会多出来更多占用空间。

    现在假设一个场景,公司现在项目部署的机器是 32 位的, 老板要让你将项目迁移到 64 位的系统上,但是 64 位系统比 32 位系统需要更多占用空间,应该怎么办?

    正常来说我们是不需要这一部分多余空间的,因为 jvm 已经帮你考虑好了,那就是指针压缩。

    指针压缩

    -XX:+UseCompressedOops 这个参数就是 JVM 提供的解决方案。通过压缩指针,占用的空间就会被压缩为原来的一半,节约空间。classpointer 参数大小就受到其影响。

    那么 Object o = new Object() 到底占用多少个字节?

    通过刚才内存布局的学习后,这个问题就很好回答了。面试官其实就是想问你对象的内存布局的理解是怎样的。

    我们这里就针对这个问题的结果分析下,这里分两种情况:

    • 在开启指针压缩的情况下,markword 占用 8 字节,classpoint 占用 4 字节,Interface data 无数据,总共是12字节,由于对象需要为8的整数倍,Padding 会补充4个字节,总共占用16字节的存储空间。

    • 在没有指针压缩的情况下,markword 占用 8 字节,classpoint 占用 8字节,Interface data 无数据,总共是16字节。

    了解了对象的创建过程和对象的内存布局,Object o = new Object() 占用了多少字节?这类问题就不是难事了!

    更多精彩推荐
    
    ☞告别 Windows、Android,国产操作系统合力破局
    ☞开源吞噬世界,得开发者得天下
    ☞小米 11 发布,售价 3999 元起;罗永浩回应败诉半导体公司;deepin 20.1(1010) 发布|极客头条☞苹果 M1 芯片预示着 RISC-V 完全替代 ARM?
    
    
    ☞5G与金融行业融合应用的场景探索
    
    ☞带你一文看懂 Blockchain + NoSQL数据库
    
    点分享点点赞点在看
    
Logo

20年前,《新程序员》创刊时,我们的心愿是全面关注程序员成长,中国将拥有新一代世界级的程序员。20年后的今天,我们有了新的使命:助力中国IT技术人成长,成就一亿技术人!

更多推荐