Какую знаешь отдельную часть в JMM, кроме Heap и Stack?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Java Memory Model (JMM) — компоненты памяти
Краткий ответ
Помимо Heap (кучи) и Stack (стека), в Java Memory Model есть ещё несколько критичных компонентов:
- Metaspace (раньше PermGen) — информация о классах
- String Pool — пул строк
- Code Cache — скомпилированный код (JIT)
- Direct Memory — буфер вне heap'а
- 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
Выводы
-
Metaspace — информация о классах (не Heap)
-
String Pool — пул уникальных строк (в Heap)
-
Code Cache — скомпилированный код JIT (не Heap)
-
Direct Memory — буферы вне Heap (для fast I/O)
-
PC Register — адрес текущей инструкции (служебное)
-
Правильное использование памяти:
- Мониторь Metaspace если загружаешь много классов
- Используй
intern()осторожно - Direct Memory для high-performance I/O
- Code Cache для горячего кода