Что такое Non Heap Memory?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое Non Heap Memory
Определение
Non Heap Memory (внехеповая память) — это часть памяти JVM, которая используется для целей, отличных от хранения объектов в heap. Это память, управляемая не сборщиком мусора (Garbage Collector), а JVM напрямую.
Отличие:
- Heap Memory — для объектов, управляется GC
- Non Heap Memory — для служебных нужд JVM, НЕ управляется GC
Структура памяти JVM
Операционная память (RAM)
├── Heap Memory (управляется GC)
│ ├── Young Generation (Eden, S0, S1)
│ ├── Old Generation
│ └── (объекты Java)
│
└── Non Heap Memory (НЕ управляется GC)
├── Metaspace (ранее PermGen)
│ ├── Класс-файлы
│ ├── Методы (кодовые инструкции)
│ └── Константы
│
├── Native Memory
│ ├── JVM код
│ ├── Библиотеки
│ └── Thread stacks
│
├── Code Cache
│ └── JIT-скомпилированный код
│
└── Buffer Pools
└── DirectByteBuffers и т.п.
Компоненты Non Heap Memory
1. Metaspace (ранее PermGen)
Одна из самых важных частей Non Heap Memory. Хранит метаинформацию о классах:
@Service
public class ClassLoadingExample {
// Это сохраняется в Metaspace
public static class DynamicClass {
private String name;
private int value;
public DynamicClass(String name, int value) {
this.name = name;
this.value = value;
}
}
public static void main(String[] args) throws Exception {
// Каждый загруженный класс требует место в Metaspace
// Это может привести к OutOfMemoryError: Metaspace
// Если dinamically загружать много классов:
for (int i = 0; i < 1000000; i++) {
Class<?> clazz = createDynamicClass("Class" + i);
// Каждый класс занимает место в Metaspace
}
}
// Динамическое создание класса (плохая идея для production)
static Class<?> createDynamicClass(String className) throws Exception {
// В реальности это очень опасно!
// Классы в Metaspace не удаляются (или удаляются редко)
return Class.forName(className);
}
}
Что хранится в Metaspace:
- Структура класса (поля, методы)
- Таблицы методов (vtable)
- Константный пул класса
- Аннотации
- Информация для отладки
2. Thread Stack
Каждый поток имеет свой стек (non-heap):
public class ThreadStackExample {
public static void main(String[] args) throws Exception {
// Размер стека: обычно 1 MB на Linux, 2 MB на Windows
// Настраивается: java -Xss2m App
// Если создать слишком много потоков → OutOfMemoryError
for (int i = 0; i < 1000000; i++) {
new Thread(() -> {
// Каждый поток требует 1-2 MB стека
// 1000000 потоков = 1-2 TB памяти!
Thread.sleep(Long.MAX_VALUE);
}).start();
}
}
// Пример: как локальные переменные занимают стек
public static void recursiveMethod(int depth) {
int[] largeArray = new int[1000]; // Локально на стеке
if (depth < 1000) {
recursiveMethod(depth + 1); // StackOverflowError если слишком глубоко
}
}
}
Что хранится в Thread Stack:
- Локальные переменные методов
- Ссылки на объекты (сами объекты в heap)
- Информация о вызывающей функции (call stack)
- Возвращаемые адреса
3. Code Cache
Хранит JIT-скомпилированный машинный код:
public class CodeCacheExample {
// JIT компилирует горячий код в машинный (Native Code)
// Это хранится в Code Cache (non-heap)
public static long fibonacci(int n) {
// После N вызовов этот метод скомпилируется JIT
// Результат (машинный код) хранится в Code Cache
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
public static void main(String[] args) {
// JIT скомпилирует этот метод после 10000 вызовов (по умолчанию)
for (int i = 0; i < 100000; i++) {
fibonacci(20);
}
// Машинный код fibonnacci теперь в Code Cache (non-heap)
}
}
Размеры Code Cache (примерные):
- Минимум: 160 MB
- По умолчанию: 240 MB на 64-bit JVM
- Настройка:
java -XX:ReservedCodeCacheSize=512m App
Если Code Cache переполнится → OutOfMemoryError: Requested array size exceeds VM limit
4. Direct ByteBuffer
Овнешняя память, используемая для I/O операций:
import java.nio.ByteBuffer;
public class DirectBufferExample {
public static void main(String[] args) throws Exception {
// Heap buffer — в heap памяти
ByteBuffer heapBuffer = ByteBuffer.allocate(1024);
System.out.println("Heap buffer isDirect: " + heapBuffer.isDirect());
// isDirect: false → находится в heap
// Direct buffer — в native памяти (non-heap)
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
System.out.println("Direct buffer isDirect: " + directBuffer.isDirect());
// isDirect: true → находится в native памяти
// Зачем используется Direct Buffer?
// 1. Быстрее для I/O (избегаем копирования через heap)
// 2. Взаимодействие с native кодом
// 3. Нулевое копирование (zero-copy)
// Но важно: Direct buffers НЕ собираются GC автоматически!
// Нужно вызвать явно (если возможно)
// В Java 9+: можно использовать Cleaner API
}
// Пример: работа с файлами через Direct Buffer
public static void readFileFast(String filename) throws Exception {
// Direct buffers быстрее для файловых операций
ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
// При работе с файлами DirectBuffer избегает:
// 1. Копирования из kernel buffer в heap
// 2. Пауз GC
// 3. Контекстных переключений
}
}
Non Heap vs Heap Memory
| Аспект | Heap | Non Heap |
|---|---|---|
| Управление | GC (автоматично) | JVM (напрямую) |
| Объекты Java | Да | Нет |
| Метаинфо классов | Нет | Да (Metaspace) |
| Стеки потоков | Нет | Да |
| JIT код | Нет | Да (Code Cache) |
| Ограничение памяти | -Xmx | ограничено ОС |
| OutOfMemoryError | Частая | Редкая |
| Размер | Большой | Средний/Маленький |
Мониторинг Non Heap Memory
import java.lang.management.*;
public class MemoryMonitoring {
public static void main(String[] args) {
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
// Heap Memory
MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
System.out.println("=== HEAP MEMORY ===");
System.out.println("Init: " + bytesToMB(heapUsage.getInit()) + " MB");
System.out.println("Used: " + bytesToMB(heapUsage.getUsed()) + " MB");
System.out.println("Committed: " + bytesToMB(heapUsage.getCommitted()) + " MB");
System.out.println("Max: " + bytesToMB(heapUsage.getMax()) + " MB");
// Non Heap Memory
MemoryUsage nonHeapUsage = memoryBean.getNonHeapMemoryUsage();
System.out.println("\n=== NON-HEAP MEMORY ===");
System.out.println("Init: " + bytesToMB(nonHeapUsage.getInit()) + " MB");
System.out.println("Used: " + bytesToMB(nonHeapUsage.getUsed()) + " MB");
System.out.println("Committed: " + bytesToMB(nonHeapUsage.getCommitted()) + " MB");
System.out.println("Max: " + bytesToMB(nonHeapUsage.getMax()) + " MB");
// Детально по компонентам
System.out.println("\n=== MEMORY POOLS ===");
for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) {
MemoryUsage usage = pool.getUsage();
System.out.println(pool.getName() + ":");
System.out.println(" Type: " + pool.getType());
System.out.println(" Used: " + bytesToMB(usage.getUsed()) + " MB");
System.out.println(" Max: " + bytesToMB(usage.getMax()) + " MB");
}
}
private static long bytesToMB(long bytes) {
return bytes / (1024 * 1024);
}
}
Типичные Out of Memory ошибки
// 1. OutOfMemoryError: Java heap space
// Куча переполнена
List<int[]> list = new ArrayList<>();
while (true) {
list.add(new int[1000000]); // Память кончится
}
// 2. OutOfMemoryError: Metaspace
// Заполнена метаспейс (много классов)
for (int i = 0; i < 1000000; i++) {
Class<?> clazz = Class.forName("someclass");
}
// 3. OutOfMemoryError: unable to create new native thread
// Нет памяти под стеки потоков (non-heap)
while (true) {
new Thread(() -> {}).start();
}
// 4. OutOfMemoryError: Direct buffer memory
// Переполнена native память для DirectBuffers
for (int i = 0; i < 1000000; i++) {
ByteBuffer.allocateDirect(1024 * 1024);
}
Оптимизация Non Heap Memory
# Увеличить Metaspace
java -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m App
# Увеличить Code Cache
java -XX:ReservedCodeCacheSize=512m -XX:InitialCodeCacheSize=256m App
# Увеличить стеки потоков
java -Xss2m App
# Ограничить Direct Buffer память
java -XX:MaxDirectMemorySize=1g App
# Вся конфигурация вместе
java -Xmx4g -Xms2g \
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m \
-XX:ReservedCodeCacheSize=512m \
-Xss2m \
App
Заключение
Non Heap Memory — это критически важная часть JVM памяти, которая часто игнорируется разработчиками. Она включает Metaspace (информация о классах), стеки потоков, Code Cache (JIT код) и native память. В отличие от heap памяти, которая управляется GC, non-heap память управляется JVM напрямую и не очищается автоматически. Понимание её структуры необходимо для эффективного управления ресурсами и избежания OutOfMemoryError в production окружении.