03 December 2013

Integer cache

Есть такая известная задачка, что напечатает в консоль данный код:

Integer x = 127;
System.out.println(x); // 1
System.out.println(127); // 2
System.out.println((Integer) 127); // 3
System.out.println(new Integer(127)); // 4
view raw quiz1.java hosted with ❤ by GitHub
Казалось бы очевидно - везде '127', но не все так просто. В Java для оптимизации числа объектов был разработан так называемый Integer cache (некий аналог String pool):

static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
view raw syntax.java hosted with ❤ by GitHub
Аналогичный кэш существует и для оберток типа Long:

private static class LongCache {
private LongCache(){}
static final Long cache[] = new Long[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
view raw syntax.java hosted with ❤ by GitHub
По-умолчанию размер кэша - 256 и заполнен числами в интервале [-128; 127]. Но размер можно изменить:
  • -XX:AutoBoxCacheMax=XXXX - увеличивает размер кэшей (Integer, Long) до XXXX + 128, где будут храниться значения в интервале [-128; XXXX];
  • -XX:+AggressiveOpts - опция изменяет значения множества опций, среди которых увеличивает AutoBoxCacheMax до 20 000;
  • -Djava.lang.Integer.IntegerCache.high=XXXX - данная опция по аналогии с AutoBoxCacheMax увеличивает размер кэша, только в данном случае только для объектов типа Integer.
Вся хитрость задачи, приведенной в начале, заключается в том, что с помощью рефлексии можно получить доступ к внутреннему значению Integer и изменить его:

Field value = Integer.class.getDeclaredField("value");
value.setAccessible(true);
value.set((Integer) 127, 0);
view raw syntax.java hosted with ❤ by GitHub
Таким образом, мы значение '127' заменяем на '0' и код, приведенный в начале, в 1-ом и 3-ем случае выведет '0'! В 1-ом случае мы выводим измененный объект 'x', во 2-ом - будет вызван метод println(int x) и боксинг не произойдет, в 3-ем случае произойдет боксинг и возьмется значение из кэша, в 4-ом - мы явно создаем объект в обход кэша.

Такие дела,
Иван