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

Какую знаешь отдельную часть в JMM, кроме Heap и Stack?

3.0 Senior🔥 111 комментариев
#JVM и управление памятью

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

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

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

Java Memory Model (JMM) — компоненты памяти

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

Помимо Heap (кучи) и Stack (стека), в Java Memory Model есть ещё несколько критичных компонентов:

  1. Metaspace (раньше PermGen) — информация о классах
  2. String Pool — пул строк
  3. Code Cache — скомпилированный код (JIT)
  4. Direct Memory — буфер вне heap'а
  5. Memory-mapped IO — файловые области

1. Metaspace (Метапространство)

Что хранит: информацию о структуре классов, методов, полей, аннотаций.

public class Student {
    private String name;      // Описание поля - в Metaspace
    private int age;           // Описание поля - в Metaspace
    
    public Student(String name) {
        // Объект Student создаётся в Heap
        // Но информация о классе Student - в Metaspace
        this.name = name;
    }
}

// Когда класс загружается:
// 1. JVM читает .class файл
// 2. Создаёт структуру Class<Student> в Metaspace
// 3. Все instances Student хранятся в Heap

Student student = new Student("Alice");
// student — ссылка в Stack
// Student object — в Heap
// Описание класса Student — в Metaspace

Содержимое Metaspace:

Metaspace ("/dev/shm" или system RAM)
├─ Class structures
│  ├─ Student class
│  │  ├─ Method table
│  │  ├─ Field descriptors
│  │  └─ Constant pool
│  ├─ String class
│  └─ ... тысячи классов
├─ Method bytecode
├─ Constant pool data
├─ Field information
├─ Method information
└─ Annotation metadata

Heap vs Metaspace:

public class MemoryExample {
    public static void main(String[] args) {
        // Heap: 100 Student объектов
        List<Student> students = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            students.add(new Student("Student " + i));
        }
        
        // Metaspace: 1 описание класса Student
        // (для всех 100 объектов)
    }
}

// Результат:
// Heap: 100 Student objects × ~50 bytes = ~5 KB
// Metaspace: 1 Student class structure = ~2 KB (один раз)

OutOfMemoryError в Metaspace:

// ❌ Может привести к OutOfMemoryError: Metaspace
public class DynamicClassLoader {
    public static void main(String[] args) {
        ClassLoader loader = new URLClassLoader(urls);
        
        // Загружаем 100,000 разных классов
        while (true) {
            loadClassDynamically(loader);
            // Каждый класс → в Metaspace
            // Metaspace переполнится!
        }
    }
}

// Решение: ограничить Metaspace
// java -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m App

2. String Pool (Пул строк)

Что хранит: литеральные строки, уникальные объекты String.

public class StringPoolExample {
    public static void main(String[] args) {
        // Три способа создания строк
        
        String s1 = "Hello";              // В String Pool
        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 Pool находится в Heap (Java 7+)
        // Раньше был в Metaspace (PermGen)
    }
}

// Вывод:
// s1: reference to "Hello" in String Pool
// s2: same reference as s1
// s3: separate String object in Heap

Как работает String Pool:

String Pool (часть Heap)
┌──────────────────────────┐
│ "Hello"  → String#12345  │
│ "World"  → String#12346  │
│ "Java"   → String#12347  │
│ "JVM"    → String#12348  │
└──────────────────────────┘

StackTrace:    Heap:
s1 ──────────→ String#12345 (в Pool)
s2 ──────────→ String#12345 (тот же!)
s3 ──────────→ String#12349 (новый)

intern() метод:

public class StringInternExample {
    public static void main(String[] args) {
        String s1 = "Hello";           // В Pool
        String s2 = new String("Hello"); // Новый в Heap
        String s3 = s2.intern();       // Добавляет в Pool
        
        System.out.println(s1 == s2);  // false
        System.out.println(s1 == s3);  // true (intern вернул из Pool)
        
        // intern() дорого - ищет в Pool, может вызвать GC!
    }
}

3. Code Cache (Кэш скомпилированного кода)

Что хранит: машинный код, сгенерированный JIT компилятором.

Жизненный цикл Java кода:

1. Исходный код: HelloWorld.java
          ↓
2. Байт-код: HelloWorld.class
          ↓
3. JVM интерпретирует (медленно)
   После 10,000 вызовов метода:
          ↓
4. JIT компилирует в машинный код → Code Cache
   Дальше выполняется быстро!

Пример:

public class HotMethod {
    // Горячий метод (вызывается часто)
    public static int sum(int[] arr) {
        int total = 0;
        for (int num : arr) {
            total += num;  // Эта строка выполняется миллионы раз
        }
        return total;
    }
    
    public static void main(String[] args) {
        int[] arr = new int[1000];
        for (int i = 0; i < 1_000_000; i++) {
            sum(arr);  // Вызывается много раз
        }
    }
}

// Что происходит:
// Вызовы 1-10,000:     Интерпретируются (медленно)
// Вызовы 10,001+:      JIT компилирует sum() в машинный код
//                      Код помещается в Code Cache
//                      Вызовы 10,001+ выполняются быстро!

Отладка Code Cache:

# Посмотреть статистику
java -XX:+PrintCodeCache App

# Увеличить размер Code Cache
java -XX:ReservedCodeCacheSize=512m App

# Посмотреть какой код скомпилирован
java -XX:+PrintCompilation App

4. Direct Memory (Прямая память)

Что хранит: буферы, выделенные напрямую с помощью ByteBuffer.allocateDirect().

public class DirectMemoryExample {
    public static void main(String[] args) {
        // Обычный буфер (в Heap)
        byte[] heapArray = new byte[1024];  // В Heap, подвержен GC
        
        // Прямой буфер (вне Heap)
        ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
        // Память выделена вне Heap → быстрее для I/O
        // НЕ подвержена GC
        // Нужно вручную освобождать!
    }
}

// Использование:
public class FileReadExample {
    public static void main(String[] args) throws Exception {
        // Direct Memory используется для fast I/O
        FileInputStream fis = new FileInputStream("large_file.bin");
        
        // ByteBuffer в Direct Memory для быстрого чтения
        ByteBuffer buffer = ByteBuffer.allocateDirect(64 * 1024);
        
        FileChannel channel = fis.getChannel();
        while (channel.read(buffer) > 0) {
            // Обработка данных из буфера
            buffer.flip();
            // ...
            buffer.clear();
        }
    }
}

Преимущества Direct Memory:

┌─────────────────────────────────────┐
│ Heap                                │
├─────────────────────────────────────┤
│ byte[] (15MB)                       │
│  ↓ копируется → Kernel buffer       │
│  ↓ копируется → Сетевая карта       │
│ 3 копии!                            │
└─────────────────────────────────────┘
    vs
┌─────────────────────────────────────┐
│ Direct Memory (15MB)                │
│  → напрямую в Kernel buffer         │
│  → напрямую в Сетевую карту         │
│ 1 копия! Быстрее!                   │
└─────────────────────────────────────┘

5. Program Counter (PC) Register

Что хранит: адрес инструкции, которая выполняется сейчас.

Это НЕ тип памяти для данных, а служебная область каждого потока.

Логика:
- Каждый поток имеет собственный PC Register
- Там хранится адрес текущей инструкции JVM
- Когда поток переключается - PC Register сохраняется
- Потом восстанавливается, чтобы продолжить

Архитектура памяти JVM

┌──────────────────────────────────────────────────────┐
│             Java Process Memory                      │
├──────────────────────────────────────────────────────┤
│                                                      │
│  ┌─────────────────────────────────────────────┐   │
│  │ Heap (динамический размер)                  │   │
│  │ ├─ Young Generation (Eden + Survivor)       │   │
│  │ ├─ Old Generation                           │   │
│  │ └─ Зависит от GC алгоритма (G1, ZGC, etc)  │   │
│  └─────────────────────────────────────────────┘   │
│                                                      │
│  ┌─────────────────────────────────────────────┐   │
│  │ Stack (переменные, ссылки, вызовы методов)  │   │
│  │ (отдельный для каждого потока)              │   │
│  └─────────────────────────────────────────────┘   │
│                                                      │
│  ┌─────────────────────────────────────────────┐   │
│  │ Metaspace (структуры классов)               │   │
│  │ (может быть на диске - если переполнится)   │   │
│  └─────────────────────────────────────────────┘   │
│                                                      │
│  ┌─────────────────────────────────────────────┐   │
│  │ Code Cache (JIT скомпилированный код)       │   │
│  └─────────────────────────────────────────────┘   │
│                                                      │
│  ┌─────────────────────────────────────────────┐   │
│  │ String Pool (часть Heap, но отдельное)      │   │
│  └─────────────────────────────────────────────┘   │
│                                                      │
│  ┌─────────────────────────────────────────────┐   │
│  │ Direct Memory (буферы вне Heap)              │   │
│  │ (контролируется -XX:MaxDirectMemorySize)     │   │
│  └─────────────────────────────────────────────┘   │
│                                                      │
└──────────────────────────────────────────────────────┘

Операционная система

Мониторинг памяти

# Посмотреть статистику памяти
java -XX:+PrintGCDetails -XX:+PrintGCTimeStamps App

# Dump памяти
jmap -dump:live,format=b,file=heap.bin <pid>

# Анализ heap dump
jhat heap.bin

# Realtime мониторинг
jconsole  # GUI tool
jvisualvm # Advanced profiler

Частые проблемы

// OutOfMemoryError: Java heap space
public class HeapOOM {
    public static void main(String[] args) {
        List<byte[]> list = new ArrayList<>();
        while (true) {
            list.add(new byte[10_000_000]); // Heap переполнится
        }
    }
}
// Решение: -Xmx1g (увеличить heap) или оптимизировать алгоритм

// OutOfMemoryError: Metaspace
public class MetaspaceOOM {
    public static void main(String[] args) {
        // Динамическая загрузка классов
        while (true) {
            loadDynamicClass(); // Metaspace переполнится
        }
    }
}
// Решение: -XX:MaxMetaspaceSize=512m

// OutOfMemoryError: Direct buffer memory
ByteBuffer.allocateDirect(Integer.MAX_VALUE);
// Решение: -XX:MaxDirectMemorySize=1g

Выводы

  1. Metaspace — информация о классах (не Heap)

  2. String Pool — пул уникальных строк (в Heap)

  3. Code Cache — скомпилированный код JIT (не Heap)

  4. Direct Memory — буферы вне Heap (для fast I/O)

  5. PC Register — адрес текущей инструкции (служебное)

  6. Правильное использование памяти:

    • Мониторь Metaspace если загружаешь много классов
    • Используй intern() осторожно
    • Direct Memory для high-performance I/O
    • Code Cache для горячего кода
Какую знаешь отдельную часть в JMM, кроме Heap и Stack? | PrepBro