Статьи

OutOfMemoryError: не удалось создать новый собственный поток — проблема раскрыта

Как вы могли видеть из моих предыдущих уроков и примеров , проблемы Java Heap Space OutOfMemoryError могут быть сложными для точного определения и решения. OutOfMemoryError — одна из распространенных проблем, с которыми я столкнулся в производственных системах Java EE: невозможно создать новый собственный поток; ошибка, возникающая, когда JVM HotSpot не может создать новый поток Java.

В этой статье мы вернемся к этой ошибке виртуальной машины HotSpot и предоставим рекомендации и стратегии ее устранения.

Если вы не знакомы с HotSpot JVM, я сначала рекомендую вам взглянуть на высокоуровневое представление внутренней области памяти HotSpot JVM . Эти знания важны для понимания проблем OutOfMemoryError, связанных с собственным пространством памяти (C-Heap).

OutOfMemoryError: невозможно создать новый собственный поток — что это?

Давайте начнем с основного объяснения. Эта ошибка JVM HotSpot генерируется, когда внутренний собственный код JVM не может создать новый поток Java. Точнее, это означает, что собственный код JVM не смог создать новый «собственный» поток из ОС (Solaris, Linux, MAC, Windows…).
Мы можем ясно видеть эту логику из реализаций OpenJDK 1.6 и 1.7, как показано ниже:

К сожалению, в этот момент вы не получите больше подробностей, чем эта ошибка, без указания того, почему JVM не может создать новый поток из ОС…

HotSpot JVM: 32-разрядная или 64-разрядная?

Прежде чем углубляться в анализ, один фундаментальный факт, который вы должны определить из своей среды Java или Java EE, — это какая версия HotSpot VM вы используете, например, 32-битная или 64-битная.
Почему это так важно? Вскоре вы узнаете, что эта проблема JVM очень часто связана с истощением собственной памяти; либо на уровне процесса JVM, либо на уровне ОС. На данный момент, пожалуйста, имейте в виду, что:

  • 32-разрядный процесс JVM теоретически может быть увеличен до 4 ГБ (даже намного ниже в некоторых старых 32-разрядных версиях Windows).
  • Для 32-битного процесса JVM C-Heap участвует в гонке с Java Heap и пространством PermGen, например, емкость C-Heap = 2-4 ГБРазмер Java Heap (-Xms, -Xmx) — Размер PermGen (-XX : MaxPermSize)
  • 64-разрядный процесс JVM теоретически позволяет использовать большую часть доступной виртуальной памяти ОС или до 16 EB (16 миллионов ТБ)

Как вы можете видеть, если вы выделите большую кучу Java (2 ГБ +) для 32-разрядного процесса JVM, объем встроенной памяти будет автоматически уменьшен, открывая дверь для сбоев выделения собственной памяти JVM.
Для 64-битного процесса JVM вашей главной заботой, с точки зрения C-Heap JVM, является емкость и доступность физической, виртуальной и оперативной памяти ОС.

Хорошо, отлично, но как собственная память влияет на создание потоков Java?

Теперь вернемся к нашей основной проблеме. Другой фундаментальный аспект JVM, который необходимо понять, заключается в том, что потоки Java, созданные из JVM, требуют встроенной памяти от ОС. Теперь вы должны начать понимать источник вашей проблемы …
Процесс создания потока высокого уровня как ниже:

  • Новый Java-поток запрашивается из Java-программы и JDK
  • Затем собственный код JVM пытается создать новый собственный поток из ОС
  • Затем ОС пытается создать новый собственный поток согласно атрибутам, которые включают размер стека потока. Собственная память затем выделяется (резервируется) из ОС для пространства памяти собственной памяти процесса Java; при условии, что процесс имеет достаточное адресное пространство (например, 32-разрядный процесс), чтобы удовлетворить запрос
  • ОС откажется от любых дальнейших собственных потоков и выделения памяти, если размер 32-битного процесса Java исчерпал свое адресное пространство памяти, например, ограничение размера процесса 2 ГБ, 3 ГБ или 4 ГБ
  • ОС также откажется от любого дальнейшего распределения потоков и собственной памяти, если виртуальная память ОС будет исчерпана (включая истощение пространства подкачки Solaris, поскольку доступ потока к стеку может вызвать ошибку SIGBUS, приводящую к сбою JVM * http: //bugs.sun .com / view_bug.do? bug_id = 6302804

В итоге:

  • Для создания потоков Java требуется собственная память, доступная из ОС; для 32-битных и 64-битных процессов JVM
  • Для 32-битной JVM для создания потока Java также требуется память, доступная из C-Heap или адресного пространства процесса

Диагностика проблем

Теперь, когда вы немного лучше поняли родную память и создание потоков JVM, самое время взглянуть на вашу проблему. В качестве отправной точки, я предлагаю вам придерживаться подхода анализа ниже:

  1. Определите, используете ли вы HotSpot 32-разрядную или 64-разрядную JVM
  2. При обнаружении проблемы возьмите дамп потока JVM и определите, сколько потоков активно
  3. Внимательно следите за использованием размера процесса Java до и во время репликации проблемы OOM
  4. Внимательно следить за использованием виртуальной памяти ОС до и во время репликации проблем OOM; в том числе использование пространства подкачки памяти при использовании ОС Solaris

Правильный сбор данных, как указано выше, позволит вам собрать правильные точки данных, что позволит вам провести первый уровень расследования. Следующим шагом будет рассмотрение возможных шаблонов проблем и определение того, какой из них подходит для вашего проблемного случая.

Проблема № 1 — истощение C-Heap (32-битная JVM)

Исходя из моего опыта, OutOfMemoryError: неспособность создать новый собственный поток довольно распространена для 32-разрядных процессов JVM. Эта проблема часто наблюдается, когда создается слишком много потоков по сравнению с емкостью C-Heap. Анализ дампа потока JVM и мониторинг размера процесса Java позволят вам определить, является ли это причиной.

Проблема № 2 — истощение виртуальной памяти ОС (64-битная JVM)

В этом случае виртуальная память ОС полностью исчерпана. Это может быть связано с тем, что несколько 64-битных процессов JVM занимают много памяти, например, 10 ГБ + и / или другие мошеннические процессы с большим объемом памяти. Опять же, размер процесса Java и мониторинг виртуальной памяти ОС позволят вам определить, является ли это причиной.

Проблема № 3 — истощение виртуальной памяти ОС (32-битная JVM)

Третий сценарий встречается реже, но все еще можно наблюдать. Диагностика может быть немного более сложной, но ключевым моментом анализа будет определение того, какие процессы вызывают полное истощение виртуальной памяти ОС. Ваши 32-битные процессы JVM могут быть либо источником, либо жертвой, например мошеннические процессы, использующие большую часть виртуальной памяти ОС и не позволяющие вашим 32-битным процессам JVM резервировать больше собственной памяти для процесса создания потока.

Обратите внимание, что эта проблема может также проявляться как полный сбой JVM (согласно приведенному ниже образцу) при нехватке виртуальной памяти ОС или пространства подкачки в Solaris.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
#
# A fatal error has been detected by the Java Runtime Environment:
#
# java.lang.OutOfMemoryError: requested 32756 bytes for ChunkPool::allocate. Out of swap space?
#
#  Internal Error (allocation.cpp:166), pid=2290, tid=27
#  Error: ChunkPool::allocate
#
# JRE version: 6.0_24-b07
# Java VM: Java HotSpot(TM) Server VM (19.1-b02 mixed mode solaris-sparc )
# If you would like to submit a bug report, please visit:
#
 
---------------  T H R E A D  ---------------
 
Current thread (0x003fa800):  JavaThread "CompilerThread1" daemon [_thread_in_native, id=27, stack(0x65380000,0x65400000)]
 
Stack: [0x65380000,0x65400000],  sp=0x653fd758,  free space=501k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
………………

Собственное истощение памяти: симптом или первопричина?

Теперь вы понимаете свою проблему и знаете, с какой моделью вы имеете дело. Теперь вы готовы дать рекомендации по решению проблемы … а вы?

Ваша работа еще не завершена, имейте в виду, что это событие OV JVM часто является просто «симптомом» фактической основной причины проблемы. Основная причина обычно гораздо глубже, поэтому, прежде чем давать рекомендации своему клиенту, я рекомендую вам действительно выполнить более глубокий анализ. Последнее, что вы хотите сделать, это просто устранить и замаскировать симптомы. Такие решения, как увеличение физической / виртуальной памяти операционной системы или обновление всех процессов JVM до 64-разрядных, следует рассматривать только после того, как вы хорошо разберетесь в основных причинах и требованиях к емкости производственной среды.

Следующий фундаментальный вопрос, на который нужно ответить: сколько потоков было активным во время OutOfMemoryError? По моему опыту работы с производственными системами Java EE, наиболее распространенной основной причиной является на самом деле приложение и / или контейнер Java EE, пытающиеся создать слишком много потоков в данный момент времени, когда сталкиваются с несоответствующими путями, такими как поток, застрявший в удаленном вызове ввода-вывода, поток условия гонки и т. д. В этом сценарии контейнер Java EE может начать создавать слишком много потоков при попытке удовлетворить входящие клиентские запросы, что приведет к увеличению точки давления на C-Heap и выделению собственной памяти. Итог, прежде чем обвинять JVM, пожалуйста, проведите должную осмотрительность и определите, имеете ли вы первопричину проблемы с настройкой потока приложения или контейнера Java EE.

Как только вы поймете и устраните основную причину (источник создания потоков), вы сможете приступить к настройке объема памяти JVM и ОС, чтобы сделать его более отказоустойчивым и лучше «пережить» эти сценарии внезапного всплеска потока.

Рекомендации:

  • Сначала выполните анализ дампа потока JVM и определите источник всех активных потоков в сравнении с установленным базовым уровнем. Определите, что заставляет ваше приложение Java или контейнер Java EE создавать столько потоков во время сбоя
  • Убедитесь, что ваши инструменты мониторинга внимательно следят за размером ваших виртуальных машин Java и за виртуальной памятью ОС. Эти важные данные потребуются для проведения полного анализа первопричин
  • Не думайте, что вы имеете дело с проблемой нехватки памяти в ОС. Посмотрите на все запущенные процессы и определите, являются ли ваши процессы JVM источником проблемы или жертвой других процессов, потребляющих всю виртуальную память
  • Пересмотрите конфигурацию потока контейнера Java EE и размер стека потока JVM. Определите, разрешено ли контейнеру Java EE создавать больше потоков, чем может обработать ваш процесс JVM и / или ОС
  • Определите, не является ли размер кучи Java вашей 32-разрядной JVM слишком большим, что не позволяет JVM создавать достаточно потоков для выполнения ваших клиентских запросов. В этом сценарии вам придется рассмотреть возможность уменьшения размера кучи Java (если это возможно), вертикального масштабирования или обновления до 64-битной JVM

Анализ планирования мощности для спасения

Как вы, возможно, видели из моей предыдущей статьи о 10 основных причинах проблем с производительностью Java EE Enterprise , источником проблемы часто является недостаток анализа планирования емкости. Любое комплексное тестирование нагрузки и производительности также должно правильно определять потоки контейнера Java EE, требования к собственной памяти JVM и ОС для вашей производственной среды; в том числе измерения воздействия «несчастных» путей. Такой подход позволит вашей производственной среде держаться подальше от подобных проблем и приведет к лучшей масштабируемости и стабильности системы в долгосрочной перспективе.

Не забудьте поделиться!

Ссылка: OutOfMemoryError: невозможно создать новый собственный поток — проблема была демистифицирована от нашего партнера по JCG Пьера-Хьюга Шарбонно из блога по шаблонам поддержки Java EE и учебному пособию по Java .