使用 Integer 包装类创建了多少个对象?

Posted

技术标签:

【中文标题】使用 Integer 包装类创建了多少个对象?【英文标题】:How many objects are created by using the Integer wrapper class? 【发布时间】:2016-07-03 16:25:51 【问题描述】:
Integer i = 3; 
i = i + 1; 
Integer j = i; 
j = i + j; 

上面示例代码中的语句创建了多少对象,为什么?是否有任何 IDE 可以让我们看到创建了多少对象(可能处于调试模式)?

【问题讨论】:

AFAIK,任何带有 CPU 和/或内存分析器的体面的 IDE 都应该这样做;想到了 NetBeans。只需执行分析(注意也启用分析标准库类!)并查看 a)创建了多少 Integer 对象(通过查看内存分析器中使用的内存等),b)Integer c-tor 的次数调用(通过查看 CPU 分析器中的方法执行计数) 【参考方案1】:

严格正确的答案是创建的Integer 对象的数量不确定。它可能在 0 到 3 之间,或者 2561 甚至更多2,具体取决于

Java 平台3, 这是否是第一次执行此代码,以及 (可能)依赖于 int 值装箱的其他代码是否在它之前运行4

-128 到 127 的 Integer 值并不严格要求预先计算。事实上,指定拳击转换的JLS 5.1.7 是这样说的:

如果被装箱的值 p 是 int 类型的整数文字,介于 -128 和 127 之间(第 3.10.1 节)......那么让 a 和 b 成为 p 的任何两次装箱转换的结果。 a == b 总是如此。

需要注意的两点:

JLS 仅 需要此为 >>literals JLS 不强制渴望缓存值。延迟缓存也满足 JLS 的行为要求。

即使是 Integer.valueof(int) 的 javadoc 也没有指定结果被急切地缓存。

如果我们检查 Java 6 到 8 中 java.lang.Integer 的 Java SE 源代码,很明显当前的 Java SE 实现策略是预先计算值。然而,由于种种原因(见上文),这仍然不足以让我们对“有多少物体”的问题给出明确的答案。


1 - 如果在类初始化期间急切初始化缓存的 Java 版本中执行上述代码触发 Integer 的类初始化,则可能为 256。

2 - 如果缓存大于 JVM 规范要求,它可能会更多。在某些 Java 版本中,可以通过 JVM 选项增加缓存大小。

3 - 除了平台实现装箱的一般方法外,编译器还可以发现部分或全部计算可以在编译时完成或完全优化。

4 - 此类代码可能触发整数缓存的惰性或急切初始化。

【讨论】:

这不是完整的故事。这些Integer 对象是在java.lang.Integer.IntegerCache 的类初始化程序中预先计算的,但是该类的初始化是由它的第一次使用触发的,所以除非JRE 在进入main 方法之前自己使用这样的装箱值(在我的测试中它没有' t),该范围内的值的第一次装箱将触发初始化。所以说没有创建Integer 对象是不正确的,因为实际上在执行main 方法时创建了256 个Integer 对象。 @Holger - 说数字不为零也是不正确的,因为 1)我们不 >>知道>know 确实,事情会变得更加复杂。我特别提到了关于“当前一代 Oracle 和 OpenJDK Java SE JVM”的最后一部分。正如已删除的评论中所说,HotSpot 也可以删除装箱,因为它知道它的语义并且问题的代码不依赖于Integer 对象的身份。或者,如果 ij 之后不再使用,它​​可能会忽略整个操作。不同的 JVM 实现可以通过将 int 值存储在指向堆外地址范围的指针中来表示某些装箱值,而不需要任何对象…… @Holger - 从理论上讲,装箱的值可能在堆之外......但不可信。为每个引用处理这种可能性会产生不必要的 GC 开销。 开销不一定很大。例如,当前的 32 位 JVM 不支持大于 2GB 的堆,这意味着永远不会设置堆内地址的最高位。因此,如果堆外引用总是设置该位,您可以轻松对其进行测试,因为它与符号位相同,并且几乎每个 CPU 都有针对它的内在测试,其中大多数甚至在将地址加载到时免费提供它一个 CPU 寄存器。所以你可以将堆外测试与null 参考测试结合起来,无论如何你都需要它(如果>0 遍历,否则它要么是null 要么是堆外)......【参考方案2】:

编译器将Integer对象拆箱到ints,通过调用intValue()对它们进行算术运算,并调用Integer.valueOfint结果分配给Integer时将它们装箱变量,所以你的例子相当于:

Integer i = Integer.valueOf(3);
i = Integer.valueOf(i.intValue() + 1);
Integer j = i;
j = Integer.valueOf(i.intValue() + j.intValue());

赋值j = i; 是一个完全正常的对象引用赋值,它不会创建新对象。它没有装箱或拆箱,也不需要因为Integer 对象是不可变的。

valueOf 方法允许缓存对象并每次针对特定数字返回相同的实例。 需要缓存整数 -128 到 +127。对于你的起始编号i = 3,所有的编号都很小,保证会被缓存,所以需要创建的对象数为0。严格来说,valueOf 允许延迟缓存实例,而不是预先生成所有实例,因此该示例可能仍会在第一次创建对象,但如果代码在程序中重复运行,则每次创建的对象数量 平均接近0。

如果您从一个不会缓存实例的较大数量开始(例如,i = 300)怎么办?那么每次valueOf调用必须新建一个Integer对象,每次创建的对象总数3

(或者,也许它仍然是零,或者也许是数百万。请记住,编译器和虚拟机可以出于性能或实现原因重写代码,只要其行为没有以其他方式改变。因此,如果您不使用结果,它可能会完全删除上面的代码。或者如果您尝试打印j,它可能会意识到j 将始终以相同的常量值结束在上面的 sn-p 之后,因此在编译时执行所有算术,并打印一个常量值。在后台运行代码的实际工作量始终是一个实现细节。)

【讨论】:

【参考方案3】:

你可以调试 Integer.valueOf(int i) 方法自己找出来。 该方法由编译器的自动装箱过程调用。

【讨论】:

这不是寻找答案的好方法。它只告诉您在特定执行平台上发生了什么。其他平台可能会给您不同的结果。【参考方案4】:

首先:您正在寻找的答案是0,正如其他人已经提到的那样。

但是让我们再深入一点。正如斯蒂芬所说,这取决于您执行它的时间。因为缓存实际上是惰性初始化的。

如果你看一下 java.lang.Integer.IntegerCache 的文档:

缓存在首次使用时初始化。

这意味着如果这是您第一次调用您实际创建的任何 Integer:

256 个整数对象(或更多:见下文) 1 个用于存储整数的数组对象 让我们忽略存储类(和方法/字段)所需的对象。无论如何,它们都存储在元空间中。

从您第二次调用它们开始,您创建了 0 个对象。


一旦你把数字调高一点,事情就会变得更有趣。例如。通过以下示例:

Integer i = 1500; 

这里的有效选项是:0、1 或 1629 到 2147483776 之间的任何数字(这次只计算创建的整数值。 为什么?答案在Integer-Cache定义的下一句给出:

缓存的大小可以由 -XX:AutoBoxCacheMax= 选项控制。

所以你实际上可以改变实现的缓存大小。

这意味着您可以到达上面的线路:

1:如果您的缓存小于 1500,则为新对象 0:如果您的缓存之前已初始化并包含 1500,则为新对象 1629: new (Integer) - 如果您的缓存设置为正好 1500 并且尚未初始化,则为对象。然后将创建从 -128 到 1500 的整数值。 在上面的句子中,您可以在此处达到任意数量的整数对象:Integer.MAX_VALUE + 129,也就是提到的:2147483776。

记住:这仅在 Oracle / Open JDK 上得到保证(我检查了版本 7 和 8)

正如您所见,要获得完全正确的答案并不容易。但光说0就会让人开心。


PS:使用menthoned参数可以使以下说法成立:Integer.valueOf(1500) == 1500

【讨论】:

【参考方案5】:

令人惊讶的是,答案是零。

从 -128 到 +127 的所有 Integers 均由 JVM 预先计算。

您的代码创建对这些现有对象的引用

【讨论】:

请参阅Write a program that makes 2 + 2 = 5,了解如何访问这些现有对象(您真的不应该)并操纵它们以获得喜剧/灾难性效果(您真的真的不应该)。 @MT0 谢谢大家。我爱你们。有没有参考链接? @SyameshK docs.oracle.com/javase/7/docs/api/java/lang/… "此方法将始终缓存 -128 到 127(含)范围内的值,并可能缓存此范围之外的其他值。" 这是特定于 Oracle Java 还是对于其他实现(如 IBM)也必须如此? @josefx:但是:“实现可能缓存这些,懒惰或急切。” (强调我的)

以上是关于使用 Integer 包装类创建了多少个对象?的主要内容,如果未能解决你的问题,请参考以下文章

关于Integer 和Double包装类创建对象时的底层解析

java 包装成类对象

Java入门系列

包装类

包装类

java包装类