What Happens to a Java ThreadPoolExecutor When an OOM Occurs?
This article explores how a Java ThreadPoolExecutor behaves when a task triggers an OutOfMemoryError, demonstrates custom exception handling, compares the four built‑in rejection policies, and shows how to create a custom thread pool that records task execution time.
ThreadPoolExecutor Behavior When OOM Occurs
Environment: JDK 1.8. The example creates a fixed‑size ThreadPoolExecutor with three worker threads and a small queue, then forces an OutOfMemoryError by allocating large byte arrays in one task while limiting the JVM heap to
-Xms10m -Xmx10m.
<code>public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, 3, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(2));
// three tasks: two simple counters, one that allocates memory
pool.execute(() -> {
int i = 0;
while (true) {
System.out.println(Thread.currentThread().getName() + ", i = " + (i++) + "," + pool);
TimeUnit.MILLISECONDS.sleep(50);
}
});
pool.execute(() -> {
int j = 0;
while (true) {
System.out.println(Thread.currentThread().getName() + ", j = " + (j++) + "," + pool);
TimeUnit.MILLISECONDS.sleep(50);
}
});
pool.execute(() -> {
int k = 0;
List<byte[]> datas = new ArrayList<>();
while (true) {
System.out.println(Thread.currentThread().getName() + ", k = " + (k++) + "," + pool);
byte[] buf = new byte[1024 * 100];
datas.add(buf);
TimeUnit.MILLISECONDS.sleep(20);
}
});
}
</code>When the JVM is started with
-Xms10m -Xmx10m, the memory‑intensive task quickly throws an OutOfMemoryError. The pool stops the thread that caused the OOM, but the other two threads continue to run.
Custom Thread Exception Handling
<code>ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, 3, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(2),
new ThreadFactory() {
private final ThreadGroup group = new ThreadGroup("Pack-Group");
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix = "pool-custom-thread-";
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
t.setUncaughtExceptionHandler((thread, e) -> {
System.out.println("Custom thread exception handling: " + t.getName());
e.printStackTrace();
});
return t;
}
});
</code>ThreadPoolExecutor Rejection Policies
Four built‑in policies are demonstrated.
AbortPolicy
<code>ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, 3, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(2),
new ThreadPoolExecutor.AbortPolicy());
</code>When submitted tasks exceed
corePoolSize + queueCapacity, the extra tasks are rejected and a
RejectedExecutionExceptionis thrown.
CallerRunsPolicy
<code>ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, 3, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(2),
new ThreadPoolExecutor.CallerRunsPolicy());
</code>The calling thread (the thread that invoked
execute) runs the rejected task.
DiscardOldestPolicy
<code>ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, 3, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(2),
new ThreadPoolExecutor.DiscardOldestPolicy());
</code>The oldest task in the queue is discarded, and the new task is inserted at the tail.
DiscardPolicy
<code>ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, 3, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(2),
new ThreadPoolExecutor.DiscardPolicy());
</code>Rejected tasks are silently dropped; the pool continues processing remaining tasks.
Custom ThreadPool with Execution Timing
<code>public class CustomThreadPool extends ThreadPoolExecutor {
public CustomThreadPool(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
if (r instanceof Task) {
((Task) r).setStart(System.currentTimeMillis());
System.out.println(t.getName() + ", 开始执行");
}
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (r instanceof Task) {
((Task) r).times();
}
}
public void execute(Task command) {
super.execute(command);
}
public static class Task implements Runnable {
private long start;
private Callback callback;
public Task(Callback callback) { this.callback = callback; }
@Override
public void run() {
if (callback != null) { callback.callback(); }
}
public void times() {
System.out.println(Thread.currentThread().getName() + " 执行耗时:" + (System.currentTimeMillis() - start) + "ms");
}
public void setStart(long start) { this.start = start; }
}
public interface Callback { void callback(); }
}
</code>By overriding
beforeExecuteand
afterExecute, the custom pool logs the start time and prints the execution duration for each task.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.