Перейти к основному содержимому

Что вызывает java.lang.OutOfMemoryError: невозможно создать новый собственный поток

· 4 мин. чтения

1. Введение

В этом руководстве мы обсудим причину и возможные способы устранения ошибки java.lang.OutOfMemoryError: невозможно создать новый собственный поток .

2. Понимание проблемы

2.1. Причина проблемы

Большинство приложений Java являются многопоточными по своей природе , состоящими из нескольких компонентов, выполняющих определенные задачи и выполняемых в разных потоках. Однако базовая операционная система (ОС) накладывает ограничение на максимальное количество потоков , которые может создать приложение Java.

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

  1. Приложение, работающее внутри виртуальной машины Java (JVM), запрашивает новый поток
  2. Собственный код JVM отправляет запрос ОС на создание нового потока ядра.
  3. ОС пытается создать новый поток ядра, который требует выделения памяти
  4. ОС отказывает в собственном выделении памяти, потому что либо
  • Запрашивающий процесс Java исчерпал свое адресное пространство памяти
  • ОС исчерпала свою виртуальную память
  1. Затем процесс Java возвращает ошибку java.lang.OutOfMemoryError: невозможно создать новый собственный поток.

2.2. Модель распределения потоков

ОС обычно имеет два типа потоков — пользовательские потоки (потоки, созданные приложением Java) и потоки ядра . Пользовательские потоки поддерживаются выше потоков ядра, а потоки ядра управляются ОС.

Между ними существует три общих отношения:

  1. Многие-к-одному — многие пользовательские потоки сопоставляются с одним потоком ядра.
  2. One-To-One — сопоставление одного пользовательского потока с одним потоком ядра.
  3. Многие ко многим — многие пользовательские потоки мультиплексируются в меньшее или равное количество потоков ядра.

3. Воспроизведение ошибки

Мы можем легко воссоздать эту проблему, создав потоки в непрерывном цикле, а затем заставив потоки ждать:

while (true) {
new Thread(() -> {
try {
TimeUnit.HOURS.sleep(1);    
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}

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

4. Решения

Один из способов устранения этой ошибки — увеличить конфигурацию ограничения потоков на уровне ОС.

Однако это не идеальное решение, поскольку OutOfMemoryError , скорее всего, указывает на ошибку программирования. Рассмотрим другие способы решения этой проблемы.

4.1. Использование Executor Service Framework

Использование инфраструктуры службы исполнителя Java для администрирования потоков может в определенной степени решить эту проблему. Платформа службы исполнителя по умолчанию или пользовательская конфигурация исполнителя могут управлять созданием потока.

Мы можем использовать метод Executors#newFixedThreadPool , чтобы установить максимальное количество потоков, которые можно использовать одновременно:

ExecutorService executorService = Executors.newFixedThreadPool(5);

Runnable runnableTask = () -> {
try {
TimeUnit.HOURS.sleep(1);
} catch (InterruptedException e) {
// Handle Exception
}
};

IntStream.rangeClosed(1, 10)
.forEach(i -> executorService.submit(runnableTask));

assertThat(((ThreadPoolExecutor) executorService).getQueue().size(), is(equalTo(5)));

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

Поскольку в пуле потоков пять потоков, он может одновременно обрабатывать не более пяти задач.

4.2. Захват и анализ дампа потока

Захват и анализ дампа потока полезен для понимания состояния потока.

Давайте посмотрим на образец дампа потока и посмотрим, что мы можем узнать:

./ee0a004043fe1092c2cf6773c05f5664.png

Приведенный выше снимок потока получен из Java VisualVM для примера, представленного ранее. Этот снимок наглядно демонстрирует создание непрерывного потока.

Как только мы определяем, что поток создается непрерывно, мы можем захватить дамп потока приложения, чтобы определить исходный код, создающий потоки:

./d23eba6f196828b0b9e0db6fc4916711.png

На приведенном выше снимке мы можем определить код, ответственный за создание потока. Это дает полезную информацию для принятия соответствующих мер.

5. Вывод

В этой статье мы узнали об ошибке java.lang.OutOfMemoryError: невозможно создать новый собственный поток и увидели, что она вызвана чрезмерным созданием потока в приложении Java.

Мы рассмотрели некоторые решения для устранения и анализа ошибки, рассмотрев структуру ExecutorService и анализ дампа потока как две полезные меры для решения этой проблемы.

Как всегда, исходный код статьи доступен на GitHub .