1、引言
各行各业都会有面试的环节,面试中被问的问题也是多种多样,让候选人应接不暇。如果你从容不迫,将面试官带入自己的节奏,恭喜你,你上岸了;如果被虐,吊打了,很可能就失去了这个机会。
IT圈的面试经常被大家诟病说,面试造火箭、入职拧螺丝。不知道其他行业怎么样?
2、面试场景
我们来回顾一下一则面试场景:
......
面试官:你说一下Object对象的占多大内存?
候选人:系统是32位还是64位?
面试官:64位
候选人:哪有没有开启指针压缩呢?
面试官:开启了
候选人:那应该占16个字节
面试官:哪不开启指针压缩呢?
候选人:那也是占16个字节
面试官:那开启指针和不开启指针的有什么区别?
候选人:开启指针压缩压缩对象头会变小,但是启用了对齐补充。
面试官:你明天来上班吧!
......
候选人对对Java对象的内存布局,JVM(Java虚拟机)相关的知识有一定的深度,直接变被动为主动,将面试官一步步带到自己的节奏上,最终拿下这场面试。
3、新手解读
如果只停留在编程表面,而不关注逻辑底层时,心中可能就是一万只羊驼奔腾而过。心里想着这玩意要估算么,实际测一下不就知道了,Object对象大小重要么,基本不用不到它,这不是故意为难人么!算了吧,这个公司不适合我,再见!
这也是典型的面试造火箭、入职拧螺丝,平时日常开发中99.99%用不到。
其实面试官,想考察候选人对Java对象的内存布局,JVM(Java虚拟机)相关的知识。下面跟着我,一起走进JAVA的内存世界。
JAVA是基于JVM运行的,这也是JAVA语言能够跨平台的原因。JVM的内存大小,影响的代码运行的流畅度。就好比我们的手机,内存越小就越容易卡顿一样。
因此,对JVM内存的管理也至关重要。Java对象首当其中,对象的大小,创建对象的多少,直接影响着内存。对象的估算,也是高并发情况下的对内存管理的一个重要指标。
4、Java的对象结构
一个Java对象在堆内存(Heap)中的表示可以分为三个部分:
- 对象头(Header)
- 实例数据(Instance Data)
- 对齐填充(Padding)
对象头顾名思义,就是Java对象的头部信息,又分为Mark Word(存储对象哈希码、锁状态、GC分代年龄等)和Klass Pointer(指向类元数据的指针类型)。
实例数据对象中所有字段占用的内存,按类型大小分配(如int占4字节,long占8字节)。
对齐填充是一个非常有意思的模块,它是一个动态变化的元素。JVM要求对象大小必须是8字节的整数倍,填充字节用于补足。为什么要对齐补充,这其实就是JVM设计的一个最佳实践的结果,为了减少缓存行(cache line)冲突、提高局部性原理的效率等目的。
5、Java对象的大小
对Java对象头大小的影响除了系统本身以外,还有收到一个JVM的参数UseCompressedOops(是否开启指针压缩)的影响。
开启指针压缩的命令:
-XX:+UseCompressedOops
| 系统位数 | 指针压缩 | Mark Word | 指针类型 | 对象引用 |
|---|---|---|---|---|
| 32 | / | 4bytes | 4bytes | 4bytes |
| 64 | 开启 | 8bytes | 4bytes | 4bytes |
| 64 | 关闭 | 8bytes | 8bytes | 8bytes |
数据类型占用的大小:
| Type | Memory Required(bytes) |
|---|---|
| boolean | 1 |
| byte | 1 |
| short | 2 |
| char | 2 |
| int | 4 |
| float | 4 |
| long | 8 |
| double | 8 |
| array | 4 |
一个对象的大小计算公式:Mark Word + Klass Pointer + 数据类型 + 对齐补充
6、空对象的大小
我们以最流程的64为操作系统为例。按照上面的公式:
- Mark Word = 8bytes
- Klass Pointer = 4bytes(开启指针压缩) 或者 8bytes(不开启指针压缩)
- 空对象没有属性,所以数据类型这里的大小就是0
- 对齐补充取决于最后的大小是否为8的倍数。
除了对齐补充以外,其他的加起来是12或者16bytes,如果是12的话,补充对齐就会发生左右,强行补充4个字节,将对象大小调整为16bytes,如果正好是16bytes,那么补充对齐这里就是0。
所以,我们得出一个结论:在64为的系统中,空对象的大小是16个字节
7、对象大小的验证
我们如何来验证对象的大小呢,我们需要借助一个内存检测工具。
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.17</version>
</dependency>
代码展示:
@Test
void test01() {
System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());
}
学过Java的老铁都知道,任何类的顶级父级都是java.lang.Object。
8、估算对象的大小
public class ObjectSize {
private double a;
private Double b;
}
我们来估算一下,这个对象的大小。我们以开启指针压缩为例。
不算对齐补充应该是12bytes,double是数据基础数据类型,按照上面的数据类型占用的大小查看,应该是的8bytes。Double是包装类,它是对象的引用,所以也占4bytes。
所以总共的对象的大小:12 + 8 +4 =24
刚好是8的倍数,所以也不用对齐。所以该空对象的大小就是24bytes。
我们来验证一下:
9、基础数据类型VS包装类
基础数据类型我们从第4节可以看出占用的字节数,以int为例,占4个字节。
那么包装类Integer 占几个字节呢?这个取决于该对象的实例字段,该实例字段不包括静态字段,因为这个字段是共享内存的,只会存在一份。
这里只有这一个实例字段。所以包装类的大小就是:12 + 4=16个字节
所以包装类占用内存大小是基础数据类型的4倍。基础数据类型在内存节省方面完胜!
关注我的微信公众号:编程朝花夕拾,获取首发内容。