← Назад к вопросам

Где хранятся в памяти обертки примитивных типов?

1.0 Junior🔥 101 комментариев
#JVM и управление памятью#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Где хранятся обертки примитивных типов в памяти?

Краткий ответ

Обертки примитивных типов (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 байт примитива, так что производительность хуже."