I was surprised to witness the Java Executor behavior, which caused “java.lang.OutOfMemoryError: unable to create new native thread” in our application. I would like to share my surprise (i.e. problem) and resolution to it.
In order to explain the problem better, I created the following example:
[code language=”java”]
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadLeaker {
/**
* Dummy Job that just prints a statement.
*/
private class DummyJob implements Runnable {
private String jobName;
public DummyJob(String jobName) {
this.jobName = jobName;
}
@Override
public void run() {
System.out.println(this.jobName + " executed!");
}
}
public void runJobs() {
// Build an executor with core pool size 5, max pool size 5 and Queue size 5
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(5));
// Execute 10 jobs;
for (int counter = 0; counter < 10; ++counter) {
executor.execute(new DummyJob("Dummy Job – " + counter));
}
}
public static void main(String args[]) {
new ThreadLeaker().runJobs();
}
}
[/code]
1. “runJobs()” method is completed. As ‘executor’ is a local variable, it should be made available for garbage collection after the execution of the method. As variable has become out of scope.
2. All jobs that were dropped to the Executor were executed.
However surprisingly – even though both of the conditions are meet, still worker threads in the ThreadPoolExecutor instance aren’t getting garbage collected. Whenever “runJobs()” method is called, 5 new worker threads get created and remains in the memory. If “runJobs()” is called few hundred times, several hundreds of worker threads are created. This causes a memory leak in the application.
How to fix this problem?
On line number 37, one would need to invoke “shutdown()” API. So that, all the worker threads would be explicitly destroyed after execution of the jobs. So revised runJobs() method would look like:
[code language=”java”]
public void runJobs() {
// Build an executor with core pool size 5, max pool size 5 and Queue size 5
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(5));
// Execute 10 jobs;
for (int counter = 0; counter < 10; ++counter) {
executor.execute(new DummyJob("Dummy Job – " + counter));
}
// Explicitly calling shutdown API to destroy threads
executor.shutdown();
}
[/code]
What triggers this Memory Leak?
Apparently, worker threads were put on to wait for state by the Executor. Following is the excerpt from the thread dump of the program. The excerpt shows the stack trace of one of the worker thread in the Executor.
You can see the line “at java.util.concurrent.locks.LockSupport.park(LockSupport.java:
186
)". This will park the threads thus making them not to die down. Only when shutdown() API is invoked it would unpark the threads and they would die down.
[code language=”java”]
pool-1-thread-5" prio=6 tid=0x0000000007444800 nid=0x33fec waiting on condition
[0x0000000009eef000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
– parking to wait for <0x00000007ac3aff48> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
at java.util.concurrent.ArrayBlockingQueue.take(ArrayBlockingQueue.java:374)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1043)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1103)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:722)
Locked ownable synchronizers:
– None
[/code]
May 9, 2018 at 7:25 pm
What you have here is a resource leak, not a memory leak. Just like you need to close/dispose streams, sockets, database connections, and open files, you also need to dispose of a thread pool.
If it is not an option to explicitly shut down the thread pool it will be finalized and eligible for gc, once there are no running threads in it. This behavior is described in the API. I would advice against it, because relying on the finalizer to clean up resources is unreliable and not an efficient way to manage resources. Perhaps a full-fledged thread pool is not the right tool for the job here.
February 21, 2018 at 4:35 pm
Another way to fix the problem would be to make the ExecutorService a class-level variable and have runJobs() submit jobs to it. That way you only create your executor and your threads once and keep reusing them. This is much closer to how executors are meant to be used.