众所周知,Java有时使用对象池作为包装器和字符串类型,有时则不使用对象池。
例如:
Integer i1 = 1;
Integer i2 = 1;
Integer i3 = new Integer(1);
String s1 = "String";
String s2 = "String";
String s3 = new String ("String");
System.out.println("(i1 == i2) " + (i1 == i2));
System.out.println("(i2 == i3) " + (i2 == i3));
System.out.println("(s1 == s2) " + (s1 == s2));
System.out.println("(s2 == s3) " + (s2 == s3));
Execution result:
(i1 == i2) true
(i2 == i3) false
(s1 == s2) true
(s2 == s3) false正如您所看到的,原语装箱从池中获取对象,通过字符串文字创建string也从池中获取对象。这些对象实际上是同一个对象(运算符==对它们返回true )。
创建包装器和字符串的其他机制不会从池中提取对象。创建这些方法的对象实际上是不同的对象(操作符==对它们返回 false )。
让我困惑的是池被部分使用了。
如果是内存问题,为什么不一直使用这个池呢?如果这不是一个内存问题-为什么要使用它呢?
问题是-实现这种行为的原因是什么(=池的部分使用)?
这个问题是相当理论性的,但它有实际的应用-它可以帮助理解如何正确地使用自定义对象池,当然,理解Java的工作方式总是好的。
发布于 2013-01-18 16:10:17
这是一个速度问题,每次分配一个新的Integer将花费时间和内存。但出于同样的原因,在启动时分配太多内存和时间。
可悲的是,它导致了一些违反直觉的行为,正如你所发现的。
结果就是我们达成了一项奇怪的妥协。Java标准中讨论了这种行为的原因。(5.7)
如果被装箱的值p为真、假、字节、\u0000至\u007f范围内的字符或-128至127之间的int或短数,则让r1和r2是p的任意两次装箱转换的结果。r1 == r2总是这样。理想情况下,装箱给定的原语值p,总是会产生相同的引用。实际上,使用现有的实现技术,这可能是不可行的。上述规则是一种务实的妥协。上面的最后一个子句要求某些公共值总是被装箱到不可区分的对象中。实现可能缓存这些,懒惰或急切。 对于其他值,此公式不允许程序员对装箱值的标识进行任何假设。这将允许(但不需要)共享部分或所有这些引用。 这确保了在大多数常见情况下,行为将是理想的行为,而不会造成不适当的性能损失,特别是在小型设备上。例如,内存有限的实现可能会缓存所有字符和短路,以及在-32K - +32K范围内的整数和多头。
tl;博士
要使它完美地工作是不可能的,而且让它完全不起作用也太奇怪了。所以我们让它在“大部分时间”起作用。
发布于 2013-01-18 16:14:40
如果是内存问题,为什么不一直使用这个池呢?
与有时会失败的简单缓存相比,所有东西都要花费更多:您需要更复杂的数据结构(可以使用简单的和小的数组来池小整数)和算法,而且即使池对您没有帮助,您也必须始终进行检查。此外,您将汇集许多不再需要的对象,这是对内存的浪费:您需要更多(无用的)缓存条目,并且需要管理该缓存(或使无用的对象保持活动状态)。
如果这不是一个内存问题-为什么要使用它呢?
这是一个记忆问题。这种优化节省了大量的内存。汇集每个对象并不一定会减少内存的使用,因为并不是每个对象都被大量使用。这是一种交换。所采用的方法为一些常见用例节省了大量内存,而不过度地减缓其他操作或浪费大量内存。
发布于 2013-01-18 16:10:01
有各种各样的考虑。例如内存的使用,但也有语言语义。
new Integer(1);是一个显式的对象创建。用"get“运算符替换它将改变语言语义。
Integer.valueOf(1)是明确的“从池中获取,如果在池范围内”。注意,池是静态的,是用Java实现的,而不是在虚拟机中实现的。你可以查一下:java.lang.Integer$IntegerCache。我认为Java规范说,从int到Integer的转换是通过插入Integer.valueOf调用进行的。
现在,如果您查看这个缓存是如何实现的,您将注意到有一个用于缓存大小的用户可调参数。默认情况下,这个缓存只是由一个数组Integer[256]组成,该数组为-128..127保留预初始化的副本(即字节的值范围)。显然,这个值范围为一般用途提供了最佳的性能/内存折衷。
有关更多细节,请参见http://martykopka.blogspot.de/2010/07/all-about-java-integer-cache.html。
如果您花了一些时间在Java中进行数值计算,您确实会感受到自动装箱的负面影响。对于高性能的数字,第一个经验法则是避免任何类型的自动装箱。例如,GNU为原始类型提供了哈希映射和类似的结构,运行时和内存的好处是巨大的。
Integer对象占用内存16字节--是int的4倍。例如,上面的缓存占用了大约5kb的内存。这是大多数应用程序都可以浪费的东西。但是很明显,你不能对所有整数都这么做!
至于字符串,编译器需要将它们适当地存储在类文件中。那么,如何在类文件中存储字符串呢?最简单的方法是将代码重写为如下内容:
private static final char[] chars_12345 = new char[]{ 't', 'e', 's', 't'};
private static final String CONST_STRING_12345 = new String(chars_12345);它不依赖于对String类型的任何神奇处理。它只是一个包装好的原语数组。当然,您希望每个类只存储一次唯一的字符串,以减少类大小,从而减少加载时间。
https://stackoverflow.com/questions/14402794
复制相似问题