Object o = new Object() 占用了多少字节
一、对象创建过程
- 一个对象 new 出来先判断线程栈是否能分配下,如果能分配下,直接分配在栈中。
- 如果分配不下,判断该对象是否足够大;如果足够大,则直接进入老年代。
- 如果不够大,判断创建对象的线程的 TLAB(本地线程缓冲区)空间是否足够,如果足够,直接分配在 TLAB 中。
- 如果不够,则进入 Eden 区中其他空间。
- GC 清除,如果清除掉了该对象,则直接结束。
- 如果没有清除掉对象,此刻对象进入 Survivor 1 区,判断年龄是否足够大(MaxTenuringThreshold ),如果年龄足够大,则直接进入 old 区域。
- 如果年龄不够大,则进入 Survivor 2 区。然后回到第五步,循环往复。
其中还有一个动态对象年龄判定
虚拟机并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无需等到MaxTenuringThreshold 中要求的年龄。
为什么对象会选择先分配在栈中?
首先,栈是线程私有的,将对象优先分配在栈中,可以直接清将对象的所有信息、空间,当线程消亡的时候也可以直接清理这一块的 TLAB 区域。
为什么 jvm 会让大对象会直接进入老年代?
大对象需要连续的空间来存储,如果不存入老年代,对 jvm 来说就是一个负担;倘若没有足够的空间就有可能导致提前触发 gc 来清理空间来安置大对象。
为什么会选择先进入 TLAB?
TLAB 是线程本地缓冲区,TLAB 的好处就是防止不同线程创建对象选择同一块儿内存区域而产生竞争,大大降低其竞争概率。
为什么会有两个 Survivor 区?并且存活且年龄不够大的对象会从一个Survivor 区转到另一个 Survivor 区?
根据根可达算法,jvm 会寻找到所有正在使用的对象,没有使用的就是垃圾。通常来说,大部分对象都是用完就抛弃的,所以真正在 Survivor 区长时间存活的对象非常少,将这部分对象从一个 Survivor 区转到另一个 Survivor 区后,就可以直接对这个 Survivor 区进行全量的空间回收,效率会很高。
二、对象的内存布局
在 java 中对象的内存布局分为两种情况:非数组对象和数组对象,数组对象和非数组对象的区别就是它需要额外的空间存储数组的长度length。
对象头
对象头又分为 MarkWord 和 Class Pointer 两部分。
- MarkWord:包含一系列的标记位,比如轻量级锁的标记位、偏向锁标记位、gc记录信息等等。在32位系统占4字节,在64位系统中占8字节。
- ClassPointer:用来指向对象对应的Class对象(其对应的元数据对象)的内存地址。在32位系统占4字节,在64位系统中占8字节。
- Length:只在数组对象中存在,用来记录数组的长度,占用4字节。
实例数据
对象实际数据。对象实际数据包括了对象的所有成员变量,其大小由各个成员变量的大小决定。(这里不包括静态成员变量,因为它是在方法区维护的)
对其填充
Java 对象占用空间是 8 字节对齐的,即所有 Java 对象占用 bytes 数必须是 8 的倍数。这是因为当我们从磁盘中取一个数据时,不会说我想取一个字节就是一个字节,都是按照一块儿一块儿来取的。这一块大小是 8 个字节,所以为了完整,padding 的作用就是补充字节,保证对象是 8 字节的整数倍。
指针压缩
-XX:+UseCompressedOops 这个参数就是 JVM 提供的,通过压缩指针,占用的空间就会被压缩为原来的一半,节约空间。classpointer 参数大小就受到其影响。
那么 Object o = new Object() 到底占用多少个字节?
这里分两种情况:
- 在开启指针压缩的情况下,markword 占用 8 字节,classpoint 占用 4 字节,实例数据无数据,总共是12字节,由于对象需要为8的整数倍,Padding 会补充4个字节,总共占用16字节的存储空间。
- 在没有指针压缩的情况下,markword 占用 8 字节,classpoint 占用 8字节,实例数据无数据,总共是16字节。