← Назад к вопросам
Как распределяется память при создании объекта
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 позже)
}
Сравнение:
| Stack | Heap |
|---|---|
| Локальные переменные | Объекты, массивы |
| Примитивные значения | Ссылки на объекты |
| 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
Лучшие практики
- Избегай memory leaks - проверяй static references
- Используй StringBuilder вместо String конкатенации в циклах
- Помни про string interning - может помочь в некоторых случаях
- Профилируй с помощью JProfiler, YourKit перед оптимизацией
- Удаляй слушателей и observers когда больше не нужны
- Будь осторожен с thread-local variables
- Используй -Xmx правильно - не больше ¾ системной памяти
- Мониторь GC паузы - может быть проблемой для low-latency приложений
Заключение
Память в Java управляется JVM автоматически, но это не значит что можно игнорировать её распределение. Понимание heap, stack, GC и object layout критично для написания эффективного и безопасного кода.