返回资讯中心
外部精选
软件工程
#开发工具#idea#intellij-platform#java#performance

IntelliJ IDEA 实测 Java 紧凑对象头能省多少内存

来源标题:Where Did the Saved Bytes Go? Compact Object Headers in IntelliJ IDEA

在 IntelliJ IDEA 中实测 Java 27 默认启用的紧凑对象头(JEP 534)发现,它对大型真实 JVM 应用确有内存收益。传统实例头占 12 字节(8 字节 mark word 加 4 字节类指针),紧凑布局将两者压缩进单个字段。文章并未只停留在合成基准数据,而是直接评估了这一改动在 IntelliJ IDEA 内部的实际效果。

JetBrains BlogIgor Kulakov2026/11/3 · 3 天前30 分钟阅读

以下正文由 AI 翻译自 JetBrains Blog(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。

AI agent 也许能让代码产出得更快,但它们并不会让代码运行得更便宜。在管理数百万个对象的应用里,内存分配和垃圾回收依然重要。JVM 一直在多个方向上应对这些成本:ZGC 针对垃圾回收停顿,Project Leyden 解决启动和预热问题,Project Lilliput 则缩减每个对象头所占的空间。在 Java 27 中,HotSpot 默认启用紧凑对象头(Project Lilliput,JEP 534)。 合成基准测试经常能展示出更小的对象占用,但对于一个大规模、真实世界的 JVM 应用,这项改动究竟带来了什么?我们直接在 IntelliJ IDEA 内部评估了它的影响。

什么是紧凑对象头?

每个 Java 对象都带有一个对象头。它标识对象所属的类,并保存或编码用于加锁、identity hash 和垃圾回收的状态。由于每个实例都要承担这笔开销,缩小对象头就成了内存优化中一个很有吸引力的目标。 在我们测试的 64 位 HotSpot 配置中,常规的实例头占 12 字节:一个 8 字节的 mark word 和一个 4 字节的类指针。紧凑布局把两者压缩进一个 8 字节的字里。 这是否意味着应用总内存会立刻减少 10%–20%?未必。下面我们会沿着从原始对象布局和堆转储,到进程常驻内存和交互式 IDE 工作负载的路径,追踪紧凑对象头的影响,指出节省在哪些环节真正出现,又在哪些环节消失。

我们如何在 IntelliJ IDEA 中测试紧凑对象头

我们测量了三种资源需求依次递增的配置:

  • 无项目(只打开欢迎界面)。
  • 小项目:Spring Petclinic,约 3,370 行 Java/Kotlin 代码。
  • 大项目:IntelliJ IDEA monorepo,约 2,650 万行代码。 这些代码库的规模相差近四个数量级,因而提供了差异显著的索引配置和内存占用。对两种项目配置,我们都运行了一个自动化脚本,反复打开 Java 源文件、插入和删除注释、解析代码引用、保存修改并关闭编辑器。 我们使用运行在 JBR 25.0.4(JetBrains IDE 使用的 Java 运行时)上的 IntelliJ IDEA 2026.2.3,显式开启和关闭紧凑对象头。在每一组开启/关闭对比中,构建、项目快照、工作负载和 -Xmx 值都保持不变。我们在空闲主机上对每种配置运行多次,交替执行开启和关闭的顺序,以尽量减少缓存预热和顺序带来的偏差。

常驻集大小(RSS)

先从用户在一个进程看起来很大时最先注意到的指标说起:RSS(常驻集大小)。 RSS 统计的是映射到进程的物理内存页。这既包括私有内存页,也包括共享内存页(例如动态库)。由于共享页会被计入每一个映射它的进程,RSS 并不代表 IDE 独占的内存量。 我们用 ps 命令对 IDE 主进程进行采样。下表列出三次运行中各阶段中位数的平均值,单位为 MiB,括号内为各次运行中位数的最低值和最高值。 条件关闭对象头,MiB开启对象头,MiB增量 欢迎界面 (-Xmx2048m)1,226.9 [1,213.6–1,247.8]1,266.4 [1,238.8–1,294.1]+39.5 MiB(+3.22%) Petclinic 活跃 (-Xmx2048m)3,576.4 [3,442.8–3,647.1]3,531.7 [3,436.3–3,625.3]−44.7 MiB(−1.25%) Monorepo 活跃 (-Xmx4096m)7,352.6 [7,351.3–7,353.4]7,376.9 [7,339.8–7,426.1]+24.3 MiB(+0.33%) 结论:在相同的 -Xmx 限制下,紧凑对象头在整体 RSS 上没有带来统计显著或一致的下降。 这是否意味着紧凑对象头没有实际收益?要回答这个问题,我们需要深入堆内部,弄清紧凑对象头究竟如何工作,而不是假定进程内存会全面下降。

测量对象占用

进程 RSS 涵盖的内容远不止存活的 Java 堆对象。常驻页还可能包括已提交堆内未分配的空余空间、类元数据、JIT 编译代码、线程栈、native 缓冲区和 JVM 数据结构、共享库以及其他内存映射文件。因此,仅缩减对象头,未必能让进程 RSS 相应减少同样多的字节(甚至可能根本看不出变化)。 为了单独观察结构上的节省,我们比较了对象占用——同一组被捕获的对象在每种对象头布局模型下会占用多少字节。 对你的项目而言,你可以直接在 IntelliJ IDEA 中查看这一点。它的内存转储会按类显示实例数、浅大小和保留大小,让你看到堆里被什么占据。从 2026.3 EAP 起,它还提供基于 JOL 布局模型的、能感知紧凑对象头的估算。 下表对比了相同捕获堆在模型下的占用。 捕获对象集实例数常规模型紧凑模型节省内存 欢迎界面2,587,924141.533 MiB132.619 MiB8.914 MiB(6.3%) Petclinic,对象头关闭9,335,990472.304 MiB442.633 MiB29.671 MiB(6.3%) Petclinic,对象头开启 9,314,900470.790 MiB441.179 MiB29.610 MiB(6.3%) Monorepo,对象头关闭19,102,676924.818 MiB848.823 MiB75.995 MiB(8.2%) Monorepo,对象头开启19,980,536953.401 MiB873.060 MiB80.341 MiB(8.4%) 在这里,节省是看得见的:固定对象集模型让欢迎界面和 Petclinic 的对象减少约 6.3%,让 monorepo 的对象减少 8.2%–8.4%。虽然系统层面的 RSS 掩盖了这一效果,但堆内部的内存节省是真实的。

为什么收益不是固定的

为什么从对象头中去掉 4 字节,并不能让每个对象都统一节省 4 字节?因为两个大小相同的堆可能包含截然不同的对象,而对象头只是起作用的变量之一。

(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →