1. Introduction
1.绪论
In this tutorial, we’ll discuss the cause and possible remedies of the java.lang.OutOfMemoryError: unable to create new native thread error.
在本教程中,我们将讨论java.lang.OutOfMemoryError: unable to create new native thread错误的原因和可能的补救措施。
2. Understanding the Problem
2.了解问题
2.1. Cause of the Problem
2.1.问题的原因
Most Java applications are multithreaded in nature, consisting of multiple components, performing specific tasks, and executed in different threads. However, the underlying operating system (OS) imposes a cap on the maximum number of threads that a Java application can create.
大多数Java应用程序在本质上是多线程的,由多个组件组成,执行特定任务,并在不同的线程中执行。然而,底层的操作系统(OS)对一个Java应用程序可以创建的最大线程数施加了一个上限。
The JVM throws an unable to create new native thread error when the JVM asks the underlying OS for a new thread, and the OS is incapable of creating new kernel threads also known as OS or system threads. The sequence of events is as follows:
当JVM向底层操作系统请求一个新的线程,而操作系统没有能力创建新的内核线程(也称为OS或系统线程)时,JVM会抛出一个无法创建新的本地线程错误。 事件的顺序如下。
- An application running inside the Java Virtual Machine (JVM) requests for a new thread
- The JVM native code sends a request to the OS to create a new kernel thread
- The OS attempts to create a new kernel thread which requires memory allocation
- The OS refuses native memory allocation because either
- The requesting Java process has exhausted its memory address space
- The OS has depleted its virtual memory
- The Java process then returns the java.lang.OutOfMemoryError: unable to create new native thread error
2.2. Thread Allocation Model
2.2.线程分配模型
An OS typically has two types of threads – user threads (threads created by a Java application) and kernel threads. User threads are supported above the kernel threads and the kernel threads are managed by the OS.
一个操作系统通常有两种类型的线程–用户线程(由Java应用程序创建的线程)和内核线程。用户线程被支持在内核线程之上,而内核线程则由操作系统管理。
Between them, there are three common relationships:
在他们之间,有三种共同的关系。
- Many-To-One – Many user threads map to a single kernel thread
- One-To-One – One user thread map to one kernel thread
- Many-To-Many – Many user threads multiplex to a smaller or equal number of kernel threads
3. Reproducing the Error
3.重现错误
We can easily recreate this issue by creating threads in a continuous loop and then make the threads wait:
我们可以通过在一个连续的循环中创建线程,然后让线程等待来轻松地重现这个问题。
while (true) {
new Thread(() -> {
try {
TimeUnit.HOURS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
Since we are holding on to each thread for an hour, while continuously creating new ones, we will quickly reach the max number of threads from the OS.
由于我们对每个线程都要坚持一个小时,同时不断创造新的线程,所以我们将很快达到操作系统的最大线程数。
4. Solutions
4.解决方案
One way to address this error is to increase the thread limit configuration at the OS level.
解决这个错误的方法之一是在操作系统层面增加线程限制配置。
However, this is not an ideal solution because the OutOfMemoryError likely indicates a programming error. Let’s look at some other ways to solve this problem.
然而,这并不是一个理想的解决方案,因为OutOfMemoryError很可能表示一个编程错误。让我们看看解决这个问题的一些其他方法。
4.1. Leveraging Executor Service Framework
4.1.利用执行器服务框架
Leveraging Java’s executor service framework for thread administration can address this issue to a certain extent. The default executor service framework, or a custom executor configuration, can control thread creation.
利用Java的执行器服务框架进行线程管理可以在一定程度上解决这个问题。默认的执行器服务框架或自定义的执行器配置可以控制线程创建。
We can use the Executors#newFixedThreadPool method to set the maximum number of threads that can be used at a time:
我们可以使用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)));
In the above example, we first create a fixed-thread pool with five threads and a runnable task which makes the threads wait for one hour. We then submit ten such tasks to the thread pool and asserts that five tasks are waiting in the executor service queue.
在上面的例子中,我们首先创建了一个有五个线程的固定线程池和一个可运行任务,使线程等待一小时。然后我们向线程池提交十个这样的任务,并断言五个任务在执行者服务队列中等待。
Since the thread pool has five threads, it can handle a maximum of five tasks at any time.
由于线程池有五个线程,它在任何时候最多可以处理五个任务。。
4.2. Capturing and Analyzing the Thread Dump
4.2.捕获和分析线程转储
Capturing and analyzing the thread dump is useful for understanding a thread’s status.
捕获和分析线程转储对于了解线程的状态非常有用。
Let’s look at a sample thread dump and see what we can learn:
让我们看看一个样本线程转储,看看我们能学到什么。
The above thread snapshot is from Java VisualVM for the example presented earlier. This snapshot clearly demonstrates the continuous thread creation.
上面的线程快照是来自于前面介绍的例子的Java VisualVM。这个快照清楚地表明了连续的线程创建。
Once we identify that there’s continuous thread creation, we can capture the thread dump of the application to identify the source code creating the threads:
一旦我们确定有连续的线程创建,我们就可以捕获应用程序的线程转储,以确定创建线程的源代码。
In the above snapshot, we can identify the code responsible for the thread creation. This provides useful insight to take appropriate measures.
在上述快照中,我们可以确定负责创建线程的代码。这为采取适当的措施提供了有用的洞察力。
5. Conclusion
5.总结
In this article, we learned about the java.lang.OutOfMemoryError: unable to create new native thread error, and we saw that it’s caused by excessive thread creation in a Java application.
在这篇文章中,我们了解了java.lang.OutOfMemoryError: unable to create new native thread错误,我们看到它是由Java应用程序中过度创建线程造成的。
We explored some solutions to address and analyze the error by looking at the ExecutorService framework and thread dump analysis as two useful measures to tackle this issue.
我们探讨了一些解决和分析错误的方案,将ExecutorService框架和线程转储分析作为解决这一问题的两个有用措施。
As always, the source code for the article is available over on GitHub.
一如既往,该文章的源代码可在GitHub上获取。