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

Как распределяется память при создании объекта

2.7 Senior🔥 141 комментариев
#Многопоточность

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

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

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

Распределение памяти в Java

Понимание того, как Java распределяет память — критично для написания эффективного кода. Это включает heap, stack, garbage collection и другие аспекты.

JVM Memory Regions

PRI JVM загружается, она создаёт несколько областей памяти:

┌─────────────────────────────────────────┐
│          JVM Memory Layout              │
├─────────────────────────────────────────┤
│                                         │
│  Heap (Кучa) ← Объекты, массивы      │
│  ├─ Young Generation                    │
│  │  ├─ Eden Space                       │
│  │  ├─ Survivor Space 0                 │
│  │  └─ Survivor Space 1                 │
│  └─ Old Generation (Tenured)            │
│                                         │
├─────────────────────────────────────────┤
│                                         │
│  Stack (Стек) ← Локальные переменные  │
│  Каждый thread имеет свой Stack        │
│  ├─ Frame 1 (main)                      │
│  ├─ Frame 2 (someMethod)                │
│  └─ Frame 3 (currentMethod)             │
│                                         │
├─────────────────────────────────────────┤
│  Code Cache (JIT компиляция)            │
├─────────────────────────────────────────┤
│  Metaspace (Java 8+) - классы, методы   │
├─────────────────────────────────────────┤
│  Native Memory - для внутренних структур│
└─────────────────────────────────────────┘

Stack vs Heap

public void createObjects() {
    // Метод начинается - создаётся frame в stack
    
    int age = 25;                    // Stack: age = 25 (примитив)
    String name = "John";            // Stack: name указывает на Heap
                                     // Heap: создаётся String объект
    
    User user = new User("John", 25); // Stack: user указывает на Heap
                                      // Heap: создаётся User объект
    
    List<String> names = new ArrayList<>(); // Stack: names указывает на Heap
                                             // Heap: создаётся ArrayList
    names.add("Alice");                      // Heap: String добавляется в List
    
    // Когда метод заканчивается - frame удаляется со stack
    // age, name, user, names перестают быть доступны
    // Но объекты на heap остаются (будут собраны GC позже)
}

Сравнение:

StackHeap
Локальные переменныеОбъекты, массивы
Примитивные значенияСсылки на объекты
LIFO (Last In First Out)Неупорядоченное хранилище
Автоматическое освобождениеУправляется GC
Быстрый доступМедленнее, чем stack
Меньше памятиЧасто больше
StackOverflowError если переполн.OutOfMemoryError если переполн.

Создание объекта: пошагово

public class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

// Шаг 1: Выделение памяти
Person person = new Person("John", 30);

// Под капотом:
// Шаг 1a: Выделяю место на heap для Person объекта
// Шаг 1b: Инициализирую поля значениями по умолчанию (name=null, age=0)
// Шаг 1c: Вызываю конструктор (this.name="John", this.age=30)
// Шаг 1d: Возвращаю ссылку на объект
// Шаг 2: На stack кладу ссылку person указывающую на этот объект

Object Layout в памяти

Person object в памяти (с 64-bit JVM, compressed pointers):

┌──────────────────────────┐
│  Mark Word (8 bytes)     │ - информация о GC, locking
├──────────────────────────┤
│  Class Pointer (4 bytes) │ - указатель на класс (Person)
├──────────────────────────┤
│  name (4 bytes)          │ - ссылка на String объект
├──────────────────────────┤
│  age (4 bytes)           │ - значение int
└──────────────────────────┘
Всего: 20 bytes (с padding)

Object Header всегда = 12 bytes (Mark Word + Class Pointer)

Heap Generations: Young и Old

public class GarbageCollectionExample {
    public static void main(String[] args) {
        // Объекты создаются в Eden Space (Young Generation)
        for (int i = 0; i < 1000; i++) {
            User user = new User("User" + i);  // Все в Eden
        }
        // После каждого цикла: young users -> Survivor Space
        // Долгоживущие объекты -> Old Generation
    }
}

// Жизненный цикл объекта:
// 1. Создание -> Eden Space (Young Gen)
// 2. Первая Minor GC -> Survivor Space 0 (если пережил)
// 3. Вторая Minor GC -> Survivor Space 1
// 4. После нескольких Minor GC -> Old Generation (tenured)
// 5. Major GC -> Удаляется из памяти

String и Intern Pool

// String специально обрабатывается для оптимизации
String s1 = "Hello";        // String Pool (special area in heap)
String s2 = "Hello";        // Та же ссылка из Pool
String s3 = new String("Hello"); // Новый объект на heap

System.out.println(s1 == s2);        // true - одна ссылка
System.out.println(s1 == s3);        // false - разные объекты
System.out.println(s1.equals(s3));   // true - одинаковое значение

// String intern method
s3 = s3.intern();           // Добавляет в Pool или возвращает существующую
System.out.println(s1 == s3); // true - теперь одна ссылка

Метаспейс (Metadata)

// Java 8+: Метаспейс заменил PermGen
// Хранит:
// - Классы
// - Методы
// - Константы
// - Информацию о типах

// Это NATIVE MEMORY (не heap!)
// Может быть указан параметром:
// -XX:MetaspaceSize=128M
// -XX:MaxMetaspaceSize=512M

// OutOfMemoryError: Metaspace
// Обычно из-за утечки classloader'ов или dynamic proxies

Примеры утечек памяти

Утечка 1: Static Collections

// Опасно!
public class MemoryLeak {
    static List<Object> cache = new ArrayList<>();
    
    public void addToCache(Object obj) {
        cache.add(obj);  // Объекты никогда не удаляются!
    }
}

// Решение: используй WeakHashMap
public class SafeCache {
    static Map<Object, Object> cache = new WeakHashMap<>();
    
    public void addToCache(Object key, Object value) {
        cache.put(key, value);  // Удаляется когда key больше не нужен
    }
}

Утечка 2: Listeners без unsubscribe

// Опасно!
public class Button {
    private List<ClickListener> listeners = new ArrayList<>();
    
    public void addListener(ClickListener listener) {
        listeners.add(listener);  // Когда удалять?
    }
}

// Решение: используй SlotMap или явное удаление
public void removeListener(ClickListener listener) {
    listeners.remove(listener);
}

Утечка 3: Thread-local variables

public class ThreadLocalLeak {
    static ThreadLocal<byte[]> cache = ThreadLocal.withInitial(() -> new byte[10*1024*1024]);
    
    public void process() {
        cache.set(new byte[10*1024*1024]);  // Никогда не очищается!
    }
}

// Решение: всегда очищай
public void process() {
    try {
        cache.set(new byte[10*1024*1024]);
        // ...
    } finally {
        cache.remove();  // Важно!
    }
}

Garbage Collection

// GC автоматический, но можно понять что происходит
System.gc();  // Запрос (не гарантирует немедленное выполнение)

// Параметры JVM для контроля GC:
// -Xmx1g             Максимум heap памяти
// -Xms512m           Начальный размер heap
// -Xmn256m           Размер Young Generation
// -XX:+UseG1GC       Использовать G1 GC
// -XX:+PrintGCDetails Логировать GC события

// Примеры GC логов:
// [GC (Allocation Failure) 512M->256M(1024M)]
// [Full GC (Ergonomics) 512M->100M(1024M)]

Shallow vs Deep Size

// Shallow size - только сам объект
String s = "Hello";
// Shallow size: ~40 bytes

// Deep size - включает все referenced объекты
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
// Deep size: ArrayList + String objects = ~100+ bytes

Лучшие практики

  1. Избегай memory leaks - проверяй static references
  2. Используй StringBuilder вместо String конкатенации в циклах
  3. Помни про string interning - может помочь в некоторых случаях
  4. Профилируй с помощью JProfiler, YourKit перед оптимизацией
  5. Удаляй слушателей и observers когда больше не нужны
  6. Будь осторожен с thread-local variables
  7. Используй -Xmx правильно - не больше ¾ системной памяти
  8. Мониторь GC паузы - может быть проблемой для low-latency приложений

Заключение

Память в Java управляется JVM автоматически, но это не значит что можно игнорировать её распределение. Понимание heap, stack, GC и object layout критично для написания эффективного и безопасного кода.