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

Что такое Non Heap Memory?

2.2 Middle🔥 171 комментариев
#Stream API и функциональное программирование#Многопоточность

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

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

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

Что такое 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

АспектHeapNon 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 окружении.

Что такое Non Heap Memory? | PrepBro