☰
new Object()到底占多大内存?揭开JVM对象内存布局真相
2026/10/20 18:42:50 网站建设 项目流程

前阵子一个朋友去某大厂一面,被问了一道看起来特别基础的题:new Object() 在 JVM 里到底占多大内存?他脱口而出“8 字节”,因为印象中对象头就是 8 字节。面试官没肯定也没否定,又追问了一句:如果堆超过 32G 呢?如果指针压缩关掉呢?他当场就有点懵。

回来找我复盘,我说你第一步就答偏了:对象头不是 8 字节,Mark Word 才是 8 字节,对象头上还挂着类型指针。这道题表面问“new Object() 多大”,实际上是在考你有没有把 JVM 对象内存布局、指针压缩、对象对齐、JOL 工具这一整条链路搭起来。答好了,后面关于内存优化、GC 的问题基本都能顺着答下去。

1. 面试题到底在考什么

1.1 一句话答案先给出来

在大多数 64 位 JVM 默认参数下,new Object()的实际占用是16 字节。

拆开看是这样的:

部分字节数说明
Mark Word8存放 hashCode、GC 分代年龄、锁状态标志等
类型指针4开启指针压缩时,指向类元数据的指针为 4 字节
实例数据0Object 没有字段
对齐填充4让对象大小成为 8 的倍数

也就是说对象头一共 12 字节,但 JVM 分配时会按 8 字节对齐,所以最终分配 16 字节。

这里最容易踩的坑是:把 Mark Word 当成整个对象头。Mark Word 只是对象头的一部分,真正的对象头还包含 Klass Pointer,也就是用来定位“这个对象到底是什么类”的指针。

1.2 为什么答案不是“一个固定值”

很多背过八股的人会直接背一句“new Object() 是 16 字节”,这本身没错,但不严谨。JVM 规范只规定了对象的“外部行为”,并没有强制规定每个对象的内存布局长什么样,布局是由具体的 JVM 实现自己定的。

所以同一个new Object(),在不同环境里可能是:

  • 32 位 JVM:对象头 8 字节,总大小 8 字节。
  • 64 位 JVM,开启指针压缩:对象头 12 字节,对齐后 16 字节。
  • 64 位 JVM,关闭指针压缩:对象头 16 字节,总大小 16 字节。

如果对象还有字段,差异会更明显。比如一个带int字段的对象,开启指针压缩时是 12 + 4 = 16 字节,关闭指针压缩后是 16 + 4 = 20 字节,对齐后变成 24 字节,差距一下子就大了。

这道题真正想考察的,是你愿不愿意承认“JVM 参数会影响结论”,并且能不能给出可信的验证方式。

2. Object 对象在 JVM 里的真实内存布局

2.1 Mark Word 和类型指针各司其职

先捋一下对象布局的基本公式:

对象内存 = 对象头(Mark Word + 类型指针) + 实例数据 + 对齐填充

Mark Word 的 8 字节存了很多东西:对象的 hashCode、GC 分代年龄、偏向锁的线程 ID、轻量级锁和重量级锁的标志位。它更像是一块“多功能标签”,空间固定不变,内容会根据锁状态和 GC 状态来回变化。

类型指针也叫 Klass Pointer,指向方法区里的类元数据,JVM 靠它知道这个对象是Object还是ArrayList。开启指针压缩时是 4 字节,关闭后是 8 字节。

对于new Object()来说,没有实例数据,所以真正参与计算的就只有这两部分。

为什么 12 字节要补齐成 16?因为 HotSpot 系 JVM 默认要求对象起始地址按 8 字节对齐。牺牲 4 字节,换来的好处是 CPU 访问更快、GC 扫描对象更高效、缓存行利用更友好。这跟“内存以 8 字节为单位”的硬件设计非常相关。

2.2 不同 JVM 参数下的尺寸变化

我把常见场景整理成了表格,方便面试时直接念:

场景Mark Word类型指针实例数据对齐前对齐后
64 位 JVM,开启指针压缩8401216
64 位 JVM,关闭指针压缩8801616
32 位 JVM44088

注意一个细节:在 64 位 JVM 上,开启指针压缩时new Object()是 16 字节,关闭指针压缩后依然还是 16 字节。这不是巧合,而是因为空对象没有实例数据,少了压缩省下来的 4 字节又被对齐填充填回去了。

但一旦对象里有引用字段,指针压缩的效果就会立刻体现出来。比如:

class Holder { Object ref; }

开启指针压缩时,Mark Word 8 + 类型指针 4 + 引用字段 4 = 16 字节,刚好对齐。

关闭指针压缩时,Mark Word 8 + 类型指针 8 + 引用字段 8 = 24 字节,每个持有引用字段的对象都比压缩时多 8 字节。

2.3 用 JOL 验证,而不是拍脑袋

面试中提到工具链,最稳的就是 JOL(Java Object Layout)。它可以直接输出一个对象在 JVM 里的具体布局。

下载 JOL 命令行工具后,可以这样跑:

java -jar jol-cli.jar internals java.lang.Object

输出类似:

java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 8 (object header: mark) 0x000000... 8 4 (object header: class) 0x... 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

可以看到,JOL 明确告诉你“对象头”和“对齐损失”各是多少。面试时如果能说出“我用 JOL 验过”这句话,比单纯背答案要有说服力得多。

3. 从 Object 延伸到对象体的内存估算

3.1 普通 Java 对象实例怎么算

new Object()只是最简单的情况,面试官很喜欢立刻把问题升级到“那一个自定义对象呢?”

估算自定义对象时,流程很固定:

  1. 算对象头:默认 12 字节(开启压缩)或 16 字节(关闭压缩)。
  2. 算实例数据:把非静态字段占的字节数加起来。
  3. 对齐到 8 的倍数。

举个栗子:

class Point { int x; int y; }

开启指针压缩时,对象头 12 字节,两个int一共 8 字节,加起来 20 字节,对齐后是 24 字节。

再比如:

class LongWrapper { long value; }

对象头 12 +long8 = 20,对齐后 24。long本身要 8 字节对齐,所以这里也会有内部填充。

需要提醒的是,static 字段不属于实例数据,所有实例共享一份,不会算在单个对象头上。父类字段也要算进去,JVM 会把这些字段一并布局在当前对象里。

3.2 数组、String 这些“特殊对象”怎么算

数组也是一种对象,但对象头里多了一个length字段。所以数组的对象头默认是:

  • Mark Word:8 字节
  • 类型指针:4 字节(压缩时)
  • 数组长度:4 字节
  • 合计:16 字节

然后再加上实际元素占用的空间,最后对齐到 8 的倍数。

例如:

new byte[10]

16 字节对象头 + 10 字节数据 = 26 字节,对齐后 32 字节。

new Object[10]

16 字节对象头 + 10 个引用字段 × 4 字节 = 56 字节,刚好是 8 的倍数。

String更特殊,因为它内部还有一个byte[]数组字段。JDK 9 之后String通常由byte[] value、int hash、byte coder等组成,所以一个空String本身可能在 24 字节左右,还要再加上底层byte[]对象的开销,整体算下来一个空字符串就接近 40 多字节。

这里也是一个隐藏考点:很多人以为String就是几个字符的内存,其实它有两层对象。

3.3 对象头里的锁状态和 GC 标记为什么影响内存

Mark Word 的 8 字节里有锁状态和 GC 标记,但它不影响对象大小。无锁、偏向锁、轻量级锁、重量级锁,Mark Word 编码内容不同,但字段长度都一样是 8 字节。

GC 的分代年龄也记在 Mark Word 里,每次 YGC 后对象年龄加一,也是在固定空间里写数字,不会让对象变大。

如果在面试里答“加了锁对象会变大”,面试官大概率会皱眉。合理的说法是:锁状态的不同会改变 Mark Word 里几个比特位的含义,但不会改变对象头的大小。

4. 面试官真正想听的答题思路

4.1 先给结论,再给前提

面试官抛出问题后,不要急着报一个数字。最好的结构是:

“如果你们默认是 64 位 JVM、堆小于 32G、指针压缩开启,new Object() 实际占用 16 字节。对象头 12 字节,对齐后 4 字节填充。如果把指针压缩关闭,空 Object 本身也是 16 字节,但一旦有引用字段,会明显涨上去。”

这个回答有两层好处:

第一,先给了明确结论,面试官不用猜你卡在哪儿。

第二,通过“前提条件”展示你理解 JVM 参数对内存布局的影响,而不是背题。

4.2 证明你懂 JVM 参数和工具链

紧接着可以补一句:

“默认开启的是 UseCompressedOops 和 UseCompressedClassPointers,可以用 PrintFlagsFinal 确认。”

例如:

java -XX:+PrintFlagsFinal -version | grep -E "UseCompressedOops|UseCompressedClassPointers|ObjectAlignmentInBytes"

然后进一步解释压缩指针的阈值:

“压缩指针把 64 位引用编码成 32 位偏移,对象默认按 8 字节对齐,所以最大能寻址 2^32 × 8 = 32G。堆超过 32G 之后,引用字段就没法继续压缩了。”

这段内容一旦说出来,基本就能和“背答案”的人拉开差距。

4.3 答好这题的三个细节

细节一:Mark Word 不等于对象头。很多人说“对象头 8 字节”,那是只记住了 Mark Word,忽略了 Klass Pointer。

细节二:对齐填充也是“真实占用”。JOL 输出的 “loss due to the next object alignment” 不代表没占用,分配器是真的会给这块空间的。

细节三:对象本身不等于引用。new Object()返回的是引用,引用在栈上可能 4 字节或 8 字节,但这不能算在堆对象占用里。面试如果把局部变量引用也算进去,概念就混了。

5. 我在实际项目中踩过的坑

5.1 new Object() 内存虽小,但架不住量大

有一个内存型服务,代码里用new Object()当锁对象标记业务状态,每次请求都 new 一个。线上堆里 Object 实例轻松堆到几百万,压测一上来 GC 秒秒钟疯狂。

算一下账:1000 万个new Object()就是 1000 万 × 16 字节 ≈ 160MB。4000 万就是 640MB。这个量级在普通 4G 堆里非常致命。

后来改成 static final 的锁对象,或者直接用并发工具类里的状态判断,内存立刻降下来。

这个场景很常见,很多性能问题不是“数据结构有多重”,而是“对象数量太多了”。

5.2 对齐填充带来的隐性浪费

再看另一种浪费:对象字段很小,却被垫了很多对齐字节。

比如:

class Status { boolean flag; }

开启压缩时:对象头 12 + 1 = 13,对齐后 16,等于浪费了 3 个字节。

看起来每个对象才丢 3 字节,但如果你有上千万个小对象,就是 30MB 的隐形开销。这还不算每个对象本身的头部成本。

所以做内存优化时,脑子里要有“对象头 + 对齐”这个概念。很多时候把多个小字段合并成一个状态位,或者批量用数组存int,内存效果比堆一堆小对象好得多。

5.3 用 JOL 做内存分析的正确姿势

我在项目里做内存优化时,最常用的工具就是 JOL。用法很简单:

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

然后在本地打印对象布局:

System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());

自定义对象也能看:

System.out.println(ClassLayout.parseClass(Point.class).toPrintable());

JOL 会告诉你字段偏移、内部填充、外部对齐损耗,非常适合验证“我以为的对象大小”和“真实的对象大小”之间的差距。

注意一个使用习惯:线上不要随便对每个对象打印 JOL,会打爆日志。一般是在本地或者压测环境跑,拿到结论后再回到线上对比堆转储。

6. 常见追问与排查技巧实录

6.1 面试官可能继续问什么

面试官听完你的“16 字节”答案后,常见追问包括:

问:数组对象多大?

答:数组头默认 16 字节(Mark Word 8 + 类型指针 4 + 长度 4),再叠加元素实际大小和对齐。

问:为什么 32G 是个分界线?

答:压缩指针用 32 位表示对象偏移,对象按 8 字节对齐,所以理论上最大能表示 2^32 × 8 = 32G。堆超过这个值,普通对象引用就得回到 64 位,内存占用会明显增加。

问:String 在内存里怎么算?

答:String 对象本身加底层 byte[] 数组。byte[] 是数组对象,有自己的对象头和数据区,不能只按字符数量估。

问:锁状态会改变对象大小吗?

答:不会。Mark Word 大小固定,锁状态改变的是这 8 字节里某些位的含义。

6.2 对象占用内存问题速查表

对象默认压缩时估算说明
new Object()16 字节对象头 12 + 对齐填充 4
class Point { int x, y; }24 字节对象头 12 + 8 数据,对齐
class Holder { Object ref; }16 字节对象头 12 + 引用 4
new byte[10]32 字节数组头 16 + 10 数据,对齐
new Object[10]56 字节数组头 16 + 10 × 4 引用
class BooleanBox { boolean b; }16 字节对象头 12 + 1 数据,对齐

这张表不用背,关键是掌握计算方法。面试时临时算出来,比背结论更让人信服。

6.3 我自己答题时的经验

我在实际工作中最大的体会是:别把 Java 对象看成“字段的集合”,它至少有 12 到 16 字节的“包装成本”。很多内存问题,尤其是高频对象和小对象堆积,罪魁祸首不是字段本身,而是这份包装成本。

所以现在写代码,凡是看到有人用new Object()当锁或者当占位标记,我都会多想一秒:这个对象数量会不会膨胀?能不能用基本类型或者复用某个已有对象来代替?

这道题后来我复盘过很多次,发现它就像一把钥匙,能打开 JVM 内存布局、指针压缩、对齐规则、JOL 工具、数组和 String 的内存模型这一整套知识。如果你正在准备面试,哪怕没有被问到这题,我也建议你亲手跑一次 JOL,看看自己常用的几个类到底占了多少内存。这个动作本身,就能帮你避开不少线上内存陷阱。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询