来源 | 哪儿来的moon(ID:onetraveller_llxz)
面试的时候,要是面试官问了标题那种问题,你知道怎么解答吗?
对象的创建过程
这张图其实就能完整的说明一个对象的创建过程到底发生了什么。从左上角开始,一步一步来:
-
判断创建对象的线程的 TLAB(本地线程缓冲区)空间是否足够;
-
-
如果不够,则进入 Eden 区中其他空间。然后进行第四步。
此刻对象进入 Survivor 1 区,判断年龄是否足够大;
-
-
如果年龄不够大,则进入 Survivor 2 区,然后进入第4步,循环往复。
通过这张流程图的步骤解析之后,大家应该对一个对象的创建过程有一个相对清晰的思路了
。但其实会有很多小细节容易被忽略,这也就是为什么 jvm 会在对象的创建过程中不遗余力的解释,细分多种情况。
为了让大家更深入地理解,我们再来看看下面几个问题:
-
为什么对象会选择先分配在栈中?
首先,栈是线程私有的,将对象优先分配在栈中,可以
通过 pop 直接将对象的所有信息、空间直接清除
,当线程消亡的时候也可以直接清理这一块的 TLAB 区域。
-
为什么 jvm 会让大对象会直接进入老年代?
大对象需要连续的空间来存储,如果不存入老年代,对 jvm 来说就是一个负担;倘若没有足够的空间就有可能导致提前触发 gc 来清理空间来安置大对象。
-
为什么会选择先进入 TLAB?
TLAB 是线程本地缓冲区,TLAB 的好处就是防止不同线程创建对象选择同一块儿内存区域而产生竞争,大大降低其竞争概率。
-
为什么会有两个 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
Padding
小贴士:
moon 在上文特意标注了 32 位系统和 64 位系统不同区域占用空间大小的区别,是因为对象指针在 64 位 JVM 下的寻址更长,所以相比 32 位会多出来更多占用空间。
现在假设一个场景,公司现在项目部署的机器是 32 位的, 老板要让你将项目迁移到 64 位的系统上,但是 64 位系统比 32 位系统需要更多占用空间,应该怎么办?
正常来说我们是不需要这一部分多余空间的,因为 jvm 已经帮你考虑好了,那就是指针压缩。
指针压缩
-XX:+UseCompressedOops 这个参数就是 JVM 提供的解决方案。通过压缩指针,占用的空间就会被压缩为原来的一半,节约空间。classpointer 参数大小就受到其影响。
那么
Object o = new Object()
到底占用多少个字节?
通过刚才内存布局的学习后,这个问题就很好回答了。面试官其实就是想问你对象的内存布局的理解是怎样的。
我们这里就针对这个问题的结果分析下,这里分两种情况:
了解了对象的创建过程和对象的内存布局,
Object o = new Object() 占用了多少字节?
这类问题就不是难事了!