使用 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,具体取决于
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
对象的身份。或者,如果 i
和 j
之后不再使用,它可能会忽略整个操作。不同的 JVM 实现可以通过将 int
值存储在指向堆外地址范围的指针中来表示某些装箱值,而不需要任何对象……
@Holger - 从理论上讲,装箱的值可能在堆之外......但不可信。为每个引用处理这种可能性会产生不必要的 GC 开销。
开销不一定很大。例如,当前的 32 位 JVM 不支持大于 2GB 的堆,这意味着永远不会设置堆内地址的最高位。因此,如果堆外引用总是设置该位,您可以轻松对其进行测试,因为它与符号位相同,并且几乎每个 CPU 都有针对它的内在测试,其中大多数甚至在将地址加载到时免费提供它一个 CPU 寄存器。所以你可以将堆外测试与null
参考测试结合起来,无论如何你都需要它(如果>0
遍历,否则它要么是null
要么是堆外)......【参考方案2】:
编译器将Integer
对象拆箱到int
s,通过调用intValue()
对它们进行算术运算,并调用Integer.valueOf
将int
结果分配给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 的所有 Integer
s 均由 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 包装类创建了多少个对象?的主要内容,如果未能解决你的问题,请参考以下文章