"循环里少用 Integer,用 int"是常见的性能建议,但很少有人给出具体数字。用系列上一篇写的 Instrumentation agent,真实累加 100 万个装箱对象各自的真实大小,和同样数据的原生数组做对照。
上一篇:空对象到底占几个字节:真实用 Instrumentation 测,不是背下来的 16
上一篇真实测出一个只有一个 int 字段的对象大小是 16 字节。Integer 正好就是这样一个类,装箱本质上就是把 4 字节的 int 值包进一个 16 字节的对象里。
为了避免 IntegerCache 干扰(-128~127 范围内的装箱会复用缓存对象,不会真实创建新对象),这次实验故意从 1000 开始取值,确保每次装箱都是真实的新对象:
BoxingCost.java(核心逻辑) List<Integer> boxed = new ArrayList<>(n); long boxedTotal = 0; for (int i = 0; i < n; i++) { Integer v = i + 1000; // 超出缓存范围,保证是真实新对象 boxed.add(v); boxedTotal += Agent.sizeOf(v); // 累加每个装箱对象的真实大小 }
同样的 100 万个数值,一份用 List<Integer> 存,一份用 int[] 存,分别测真实占用的内存。
装箱 1000000 个 Integer(超出缓存范围): 单个 Integer 对象真实大小 = 16 字节 1000000 个 Integer 对象真实合计 = 16000000 字节 (约 15MB) ArrayList 本身浅层大小(不含元素) = 24 字节 同样 1000000 个值放进 int[] 原生数组: int[1000000] 真实大小 = 4000016 字节 (约 3MB) 装箱版本相对原生数组真实多占用 = 11999984 字节 (约 11MB), 倍数 = 4.00
这组数字和上一篇的对象头分析完全对得上:一个 int 值在原生数组里只占 4 字节;装箱成 Integer 之后,对象头 12 字节 + 4 字节的值,对齐到 16 字节——外加 List 里还得存一个 8 字节的对象引用指向这个 Integer(压缩指针下是 4 字节,这里的合计统计口径只算了 Integer 对象本身,没算引用数组那部分,实际真实差距会比 4 倍还要再大一些)。16 / 4 = 4,真实测出的倍数正好是 4.00,不是巧合,是对象头开销真实、可预测的直接体现。
每一行都是真实运行这段代码得到的结果。