[关闭]
@liufor 2016-06-03T05:26:40.000000Z 字数 2941 阅读 1048

对 IntegerCache 的探讨

Java JVM


Integer == 的「意外」表现

代码太长,不想看,总结下来就两行代码

  1. public class App {
  2. public static void main(String[] args) {
  3. int int127 = 127;
  4. Integer integer127_1 = int127;
  5. Integer integer127_2 = int127;
  6. System.out.println(integer127_1 == integer127_2); //true, 看上去合情合理.
  7. int int128 = 128;
  8. Integer integer128_1 = int128;
  9. Integer integer128_2 = int128;
  10. System.out.println(integer128_1 == integer128_2); //false, 看上去违反直觉.
  11. }
  12. }

源码之下,了无秘密。

将同一个 int 字面量 A 赋值给两个 Integer 变量 A1和B1,然后用 == 比较这两个变量。

我们查看 Oracle/Sun javac 生成的字节码 ,发现字节码中 int 变量赋值给 Integer 变量的实际 其实是是调用了 Integer.valueOf(字面量值),ECJ也是如此。

Integer.valueOf(int)里调用的其实是 IntegerCache 。

valueOf 和 IntegerCache的代码贴图

我要断案

追本溯源

什么时候会发生基本类型的装箱?

Java语言规范规定

JLS7 5.1.7(JLS8 7 5.1.7)

If the value p being boxed is an integer literal of type int between -128 and 127 inclusive (§3.10.1), or the boolean literal true or false (§3.10.3), or a character literal between '\u0000' and '\u007f' inclusive (§3.10.4), then let a and b be the results of any two boxing conversions of p. It is always the case that a == b.

Ideally, boxing a primitive value would always yield an identical reference. In practice, this may not be feasible using existing implementation techniques. The rule above is a pragmatic compromise, requiring that certain common values always be boxed into indistinguishable objects. The implementation may cache these, lazily or eagerly. For other values, the rule disallows any assumptions about the identity of the boxed values on the programmer's part. This allows (but does not require) sharing of some or all of these references. Notice that integer literals of type long are allowed, but not required, to be shared.

This ensures that in most common cases, the behavior will be the desired one, without imposing an undue performance penalty, especially on small devices. Less memory-limited implementations might, for example, cache all char and short values, as well as int and long values in the range of -32K to +32K.

中文翻译

如果被装箱的 p 值是一个在 -128 ~ 127 之间(闭区间)的 int 类型的整数字面量,或者是布尔字面量true或false,或者是一个在 '\u0000' ~ '\u007f' 之间(闭区间)的 char 字符字面量,那么若 a 和 b 是 p 的任意两个装箱转换的结果,则 a == b 总是成立的。

理想情况下,装箱给定的简单类型的值 p 总是会产生同一个引用。但是在实际中,对于现有实现技术而言,也许并不可行,因此,上面这些规则是务实妥协的参悟是。上面这段话最后一个自居要求特定的常用值总是被包裹成不可区分的对象,而具体实现可以惰性地活着积极地缓存这些对象。对于其他值,这些规则不允许程序员对其被包装的值的标识做任何假设。这将允许(但并非强制)共享部分或者全部引用。注意,允许(非强制)共享long类型的全部整数字面常量。

这条规则将被确保在最常见的情况下,其行为正是用户想要的,而不会因装箱导致过多的的性能伤害,这一点在小型设备上尤其常见。例如内存受限的实现可以缓存所有 char 和 short 值,以及在 -32k ~ +32k 之间的所有int和long型值

TL;DR

任何一个简单类型(primitive)变量或者常量都可能被装箱,返回一个包装对象。这种装箱发生的频率挺高的,如果每次返回的都是一个新对象的话,对内存和GC都会造成压力。

理想情况下,值相等的多个简单类型(primita)自动装箱后的结果(包装对象)应该是同一个引用。可以通过缓存的方式,将相同值相同类简单类型的包装对象统一为一个引用。

如果全部预先缓存起来也是不现实的,光是缓存全部 int型 4294967296*12字节 = 48G 内存,服务器爆掉了。

所以,最终JLS的规定是:
规定必须缓存部分,列表如下;可选缓存更大范围的值。
1. int -128~127
2. bool 全部(true,false)
3. char 0~255,涵盖所有ANSI,
4. byte 未规定
5. long 未规定
6. short 未规定
float 和 double 因为无限个数,所以不能缓存

最终的解决方案为:

  1. int 通过 IntegerCache 实现硬性规定,并留下 -XX:AutoBoxCacheMax=值 开启更大范围
  2. bool 通过2个静态常量实现硬性规定
  3. char 通过 CharacterCache CharacterCache 实现硬性规定
  4. byte 通过 ByteCache 缓存 -128~127,全部缓存
  5. long 通过 LongCache 缓存 -128~127
  6. short 通过 ShortCache 缓存 -128~127

说白了,这个事情就是因为想避免装箱引起的新包装对象创建和对GC的压力而想采取缓存包装对象,却因内存原因无力缓存包装对象的所有可能值,于是只强制规定了必须缓存3种常用类型(int,bool,char)的常用值,JDK实现的时候,都扩大了另外3种类型(byte,long,short)的常用值。

http://droidyue.com/blog/2015/04/07/autoboxing-and-autounboxing-in-java/index.html

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注