У Java-процесса свободен heap, но создание нового platform thread завершается OutOfMemoryError. Какой ресурс JVM и ОС следует искать?
Искать нужно не свободное место в Java heap, а нативные ресурсы, необходимые для создания platform thread: память под стек потока, структуры JVM и ОС, а также лимиты на число потоков. Причиной может быть исчерпание виртуальной или физической памяти, лимита потоков процесса, лимита пользователя либо ограничения контейнера.
Platform thread в Java обычно связан с отдельным потоком операционной системы. Такая модель упрощает использование блокирующего ввода-вывода, но каждый поток требует нативных ресурсов, которые не учитываются как обычные Java-объекты в heap.
Размер heap — только одна часть памяти процесса. Рядом с ним существуют стеки потоков, память JVM, нативные библиотеки, буферы и структуры операционной системы. Позднее появились виртуальные потоки, которые не требуют отдельного OS-потока на каждый экземпляр, но platform threads по-прежнему используются как carrier threads и тоже ограничены ресурсами.
Сообщение OutOfMemoryError не всегда означает, что заполнен heap. При создании потока JVM может не получить память для нативного стека или получить отказ ОС из-за ограничения количества потоков.
Неверный вывод приводит к бесполезному увеличению -Xmx: heap станет больше, но лимит OS-потоков или нативной памяти не изменится. Более того, увеличение heap может оставить меньше адресного или физического пространства для стеков и других нативных областей.
Для platform thread обычно требуются:
-Xss;-Xss задаёт размер стека Java-потока, но это не обязательно означает немедленное физическое занятие всего объёма. Однако слишком большой размер увеличивает потребность в виртуальном адресном пространстве и потенциально доступную к фиксации память для каждого потока. Слишком маленький размер повышает риск StackOverflowError, поэтому уменьшать его без проверки глубины вызовов опасно.
Диагностику нужно начинать с фактического числа потоков и динамики его роста. Затем проверяют лимиты ОС и контейнера, доступную память, виртуальное адресное пространство, размер стеков, а также наличие чрезмерного создания потоков вместо ограниченного пула.
Полезно разделять два сценария:
Native Memory Tracking и профилировщики нативной памяти помогают отделить память стеков от других областей JVM. Heap dump в такой ситуации недостаточен: он показывает Java-объекты, но не объясняет полностью отказ ОС при создании нового потока.
Сервис создаёт отдельный platform thread для каждой фоновой операции. Heap занят умеренно, но при росте очереди новые потоки перестают запускаться. Рассматриваются три варианта: увеличить heap, повысить лимиты ОС или ограничить параллелизм.
Увеличение heap не устраняет корень проблемы. Повышение лимитов может лишь отложить отказ, если архитектура продолжает создавать неограниченное число потоков. Ограниченный пул с очередью задач обычно является базовым решением: число platform threads становится предсказуемым, а нагрузка управляемой.
Если операции в основном ожидают ввода-вывода и приложение совместимо с виртуальными потоками, их можно рассмотреть как альтернативу. Но они не отменяют лимиты на carrier threads, память задач, файловые дескрипторы и внешние ресурсы. Выбор делают после проверки характера нагрузки, а не только по факту ошибки.
OutOfMemoryError при создании потока означает нехватку памяти?Нет. JVM может использовать это исключение для сообщения о неудаче создания потока, хотя непосредственной причиной стал лимит количества потоков или отказ ОС. Поэтому нужно проверять не только память, но и ограничения процесса, пользователя и контейнера.
-Xss может помочь, но не является универсальным лечением?Меньший стек снижает потенциальную потребность в памяти на platform thread и иногда позволяет создать больше потоков. Но глубоко вложенные вызовы, рекурсия или сложные цепочки фреймов могут начать завершаться StackOverflowError. Кроме того, если исчерпан именно лимит количества потоков, изменение -Xss ничего не даст.
Оно разрешает процессу создать больше потоков, но каждый поток потребляет нативные ресурсы. При отсутствии ограничения параллелизма процесс может быстрее исчерпать память, CPU, дескрипторы или возможности планировщика. Лимит ОС должен сопровождаться контролем размера пула и общей бюджетной оценкой ресурсов.