美团一面:new Object() 在 JVM 中到底占多大内存?

30 阅读5分钟

在美团、阿里等大厂的面试中,“一个 Object 对象占多少内存”是一个极其高频的题目。很多候选人认为这是一个考察“背诵能力”的冷门知识,但实际上,它考察的是你对 JVM 内存模型、对象布局以及 CPU 架构的深度理解。今天,我们就来彻底拆解这个看似简单却暗藏玄机的技术点。

一、 为什么我们要纠结这几个字节?

在日常业务开发中,我们很少关注一个对象具体占用了 16 字节还是 24 字节。但在高并发、高吞吐的场景下(如亿级流量的电商大促、海量数据的实时计算),内存就是金钱,内存就是性能

理解对象的内存布局,是进行JVM 调优排查内存溢出(OOM)以及设计高性能缓存组件的基石。

那么,当我们敲下new Object()这行代码时,JVM 堆内存中究竟发生了什么?

二、 庖丁解牛:Java 对象的内存布局

在 HotSpot 虚拟机中,一个对象在内存中的存储布局可以分为三个部分:

  1. 对象头(Header)
  2. 实例数据(Instance Data)
  3. 对齐填充(Padding)

为了更直观地理解,看下图:

1. 对象头(Header)—— 对象的“身份证”

对象头是对象最核心的部分,它包含两类信息:

  • Mark Word(标记字段)

    • 这是对象最“忙碌”的部分。它存储了对象的运行时数据,如:哈希码(HashCode)GC 分代年龄锁状态标志线程持有的锁偏向线程 ID等。
    • 在 64 位虚拟机中,Mark Word 占用8 字节
  • Klass Pointer(类型指针)

    • 对象指向它的类元数据的指针,JVM 通过这个指针来确定这个对象是哪个类的实例。
    • 它的长度取决于是否开启了指针压缩CompressedOops)。

2. 实例数据(Instance Data)—— 对象的“血肉”

这是对象真正存储有效信息的地方,即我们在代码中定义的各种字段(int, boolean, reference 等)。

  • 对于new Object()来说,因为Object类没有任何字段,所以这部分大小为0

3. 对齐填充(Padding)—— 强迫症的“占位符”

这是最容易被忽视的一点。HotSpot 虚拟机的自动内存管理系统要求。

  • 换句话说,任何对象的大小都必须是 8 的倍数
  • 如果对象头 + 实例数据不是 8 的倍数,JVM 就会用空白数据填充,直到补齐。

三、 深度计算:new Object() 到底多大?

环境假设:我们目前绝大多数服务器都是64 位 JVM。我们将分两种主要情况讨论。

情况 A:64 位 JVM + 开启指针压缩(默认情况)

从 JDK 1.6 update 14 开始,64 位 JVM 默认开启了指针压缩(-XX:+UseCompressedOops)。

  • Mark Word:8 字节

  • Klass Pointer:被压缩为 4 字节

  • 实例数据:0 字节

  • 当前总和:8 + 4 + 0 = 12 字节

注意:12 不是 8 的倍数!

对齐填充:JVM 必须强行填充4 字节,使其达到 16 字节。

结论:16 字节

情况 B:64 位 JVM + 关闭指针压缩

如果你手动设置了-XX:-UseCompressedOops,或者堆内存超过了 32GB(指针压缩会自动失效)。

  • Mark Word:8 字节

  • Klass Pointer:未压缩,占用 8 字节

  • 实例数据:0 字节

  • 当前总和:8 + 8 + 0 = 16 字节

注意:16 已经是 8 的倍数,不需要填充。

结论:还是 16 字节

情况 C:32 位 JVM(古董级环境)

  • Mark Word:4 字节
  • Klass Pointer:4 字节
  • 总计:8 字节

四、 眼见为实:JOL 工具验证

空口无凭,我们使用 OpenJDK 提供的JOL (Java Object Layout) 工具来打印对象的内存布局。

引入依赖:

<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.16</version></dependency>

测试代码:

importorg.openjdk.jol.info.ClassLayout; publicclassObjectSizeTest{ publicstaticvoidmain(String[] args) { Objectobj =newObject(); // 打印对象布局 System.out.println(ClassLayout.parseInstance(obj).toPrintable()); }}

控制台输出(JDK 1.8, 64位, 默认开启指针压缩):

java.lang.Object object internals:OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01000000(00000001000000000000000000000000) (1) 4 4 (object header) 00000000(00000000000000000000000000000000) (0) 8 4 (object header) e50100f8 (11100101000000010000000011111000) (-134217243) 12 4 (loss due to the next object alignment)Instancesize:16bytesSpacelosses:0bytes internal +4bytes external =4bytes total

五、 避坑指南:常见误区

误区 1:指针压缩能减少所有对象的大小?

真相:不一定。 就像new Object(),开启压缩是 8+4+4(填充)=16字节;关闭压缩是 8+8=16字节。对于空对象,指针压缩并没有节省内存空间,只是把“类型指针”占用的空间换成了“填充”空间。但对于包含多个引用字段的复杂对象,指针压缩效果非常显著。

误区 2:数组对象也只占 16 字节?

真相:错误。 数组对象除了对象头,还多了一个4 字节的空间来存储数组长度new int[0]在开启压缩时占用:8(Mark) + 4(Klass) + 4(Length) = 16 字节(正好不需要填充)。

误区 3:所有 CPU 缓存行都是 64 字节?

虽然常见的 x86 架构 CPU 缓存行(Cache Line)通常是 64 字节,这与对象对齐(8 字节)是两个层面的概念。对象对齐是为了让 CPU 无论是读取 32 位还是 64 位数据,都能一次性高效访问,避免跨缓存行读取。

六、 总结与建议

下次面试官问你:“new Object() 占多少内存?”

你可以自信地回答:

“在主流的 64 位 JVM 中,无论是否开启指针压缩,new Object() 都占用 16 字节

区别在于内部结构:

  • 开启压缩:8 字节 Mark Word + 4 字节 Klass Pointer + 4 字节对齐填充。
  • 关闭压缩:8 字节 Mark Word + 8 字节 Klass Pointer。”

**
**

架构师建议: 在进行海量对象存储设计(如本地缓存、对象池)时,计算内存容量千万不要只算字段大小,对象头和对齐填充的开销(Overhead)往往比你想象的要大得多