Где хранятся в памяти обертки примитивных типов?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Где хранятся обертки примитивных типов в памяти?
Краткий ответ
Обертки примитивных типов (Integer, Long, Boolean и т.д.) - это ОБЪЕКТЫ, поэтому они хранятся в HEAP. Однако есть специальное кеширование (Integer Cache), которое может сбить с толку.
Основное правило
public void example() {
int primitive = 42; // На STACK
Integer wrapped = 42; // ОБЪЕКТ на HEAP (ссылка на STACK)
Integer wrapped2 = Integer.valueOf(42); // То же самое
}
Память:
STACK:
primitive: 42
wrapped: 0xABCD (ссылка)
wrapped2: 0xABCD (ссылка) ← МОЖЕТ быть та же ссылка!
HEAP:
0xABCD (Integer object): 42
Autoboxing и Unboxing
Autoboxing (примитив → объект)
int num = 5;
Integer wrapped = num; // Автоматический вызов Integer.valueOf(5)
// Эквивалентно:
Integer wrapped = Integer.valueOf(num);
Unboxing (объект → примитив)
Integer wrapped = 5;
int num = wrapped; // Автоматический вызов wrapped.intValue()
// Эквивалентно:
int num = wrapped.intValue();
Integer Cache: подводный камень
Кеширование малых чисел
Java кеширует Integer объекты для чисел от -128 до 127:
Integer a = 100; // Из кэша
Integer b = 100; // Из кэша
System.out.println(a == b); // true! (одна ссылка)
Integer c = 128; // НЕ из кэша
Integer d = 128; // НЕ из кэша (новый объект)
System.out.println(c == d); // false! (разные ссылки)
Как это работает
// В Java источнике (Integer.java)
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)]; // Возврат из кэша
return new Integer(i); // Новый объект
}
// IntegerCache прединициализирует объекты -128..127
private static class IntegerCache {
static final Integer cache[] = new Integer[-(-128) + 127 + 1]; // 256 объектов
static {
int j = -128;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
}
Память при инициализации:
HEAP (IntegerCache инициализируется при загрузке класса):
IntegerCache.cache[0] → Integer(-128)
IntegerCache.cache[1] → Integer(-127)
...
IntegerCache.cache[255] → Integer(127)
Всего 256 объектов уже созданы в памяти!
Детальная диаграмма памяти
public void demonstrateMemory() {
Integer x = 50; // Из кэша
Integer y = 50; // Из кэша (та же ссылка)
Integer z = new Integer(50); // НОВЫЙ объект, не из кэша
Integer a = 200; // НЕ из кэша
Integer b = 200; // НЕ из кэша (разная ссылка)
}
Память:
STACK (метод demonstrateMemory):
x: 0xCACHE50 (из IntegerCache)
y: 0xCACHE50 (та же ссылка!)
z: 0x11111111 (новый объект)
a: 0x22222222 (новый объект)
b: 0x33333333 (другой новый объект)
HEAP (IntegerCache - загружается при загрузке класса Integer):
0xCACHE-128 → Integer(-128)
...
0xCACHE50 → Integer(50) ← ссылки x и y
...
0xCACHE127 → Integer(127)
HEAP (обычная):
0x11111111 → Integer(50) ← ссылка z
0x22222222 → Integer(200) ← ссылка a
0x33333333 → Integer(200) ← ссылка b
Сравнение с primitive
public void comparison() {
// ПРИМИТИВЫ (STACK)
int p1 = 100;
int p2 = 100;
System.out.println(p1 == p2); // true (сравнение значений)
// ОБЕРТКИ (HEAP)
Integer w1 = 100; // Из кэша
Integer w2 = 100; // Из кэша
System.out.println(w1 == w2); // true (одна ссылка в кэше)
System.out.println(w1.equals(w2)); // true (сравнение значений)
// ОБЕРТКИ БЕЗ КЭША
Integer w3 = 200; // Новый объект
Integer w4 = 200; // Другой новый объект
System.out.println(w3 == w4); // false! (разные ссылки)
System.out.println(w3.equals(w4)); // true (сравнение значений)
}
Другие обертки: разные кэши
// Integer: -128..127 (по умолчанию)
Integer a = 100; // кэш
Integer b = 200; // нет кэша
// Long: -128..127 (по умолчанию)
Long c = 100L; // кэш
Long d = 200L; // нет кэша
// Boolean: всего 2 значения
Boolean e = true; // кэш (singleton-подобно)
Boolean f = false; // кэш (singleton-подобно)
Boolean g = true;
System.out.println(e == g); // true! (одна ссылка)
// Byte: -128..127 (все значения кэшируются!)
Byte h = 50; // кэш
Byte i = 50; // кэш (та же ссылка)
System.out.println(h == i); // true!
// Character: 0..127
Character j = 'a'; // кэш (0..127 из \u0000 до \u007F)
Character k = 'a';
System.out.println(j == k); // true!
Character l = 'ü'; // 252 > 127
Character m = 'ü';
System.out.println(l == m); // false!
Размер памяти обертки vs примитив
public void memorySize() {
// Примитив
int primitive = 42; // 4 байта на STACK
// Обертка
Integer wrapped = 42; //
// Структура Integer на 64-bit JVM:
// - Object header: 16 байт (mark word + class pointer)
// - int value: 4 байта
// - Padding: 4 байта (выравнивание)
// = 24 байта на HEAP
// + 8 байт ссылка на STACK
// Итого: Integer занимает в 6 раз больше места!
}
Когда обертки попадают в кэш
public void cacheExamples() {
// ✅ В кэше (-128..127)
Integer a = 100; // valueOf(100) → кэш
Integer b = 50; // valueOf(50) → кэш
Integer c = -128; // valueOf(-128) → кэш
Integer d = 127; // valueOf(127) → кэш
// ❌ НЕ в кэше
Integer e = 128; // valueOf(128) → новый объект
Integer f = 200; // valueOf(200) → новый объект
Integer g = -129; // valueOf(-129) → новый объект
// ВСЕГДА новый объект (игнорирует кэш)
Integer h = new Integer(50); // Новый, даже если 50 в кэше
Integer i = new Integer(50);
System.out.println(h == i); // false!
}
Практические следствия
// ❌ ПЛОХО: полагаться на == для обертки
Integer x = someInteger();
if (x == 100) { // Может не сработать если x > 127!
// Опасно!
}
// ✅ ХОРОШО: использовать .equals()
if (x.equals(100)) { // Всегда безопасно
// Правильно
}
// ✅ ХОРОШО: разбокс и сравнить примитивы
if (x.intValue() == 100) { // Явно
// Правильно
}
Ответ на собеседовании
Правильный ответ: "Обертки примитивных типов (Integer, Long и т.д.) - это объекты, поэтому они хранятся в HEAP. Однако Java имеет специальное кеширование (Integer Cache) для чисел -128..127, которые переиспользуются. Это значит, что Integer.valueOf(100) и Integer.valueOf(100) вернут одну ссылку на один объект из кэша, но Integer.valueOf(200) и Integer.valueOf(200) создадут два разных объекта. Это важно помнить при сравнении: нельзя использовать == для сравнения обертки с числом (может сработать для -128..127, но не сработает для больших чисел). Нужно использовать .equals() или разбоксить в примитив. Обертка занимает 24 байта на HEAP, против 4 байт примитива, так что производительность хуже."