Backend Development 9 min read

Memory Overhead of execute vs submit in Java ThreadPoolExecutor

This article analyses the memory consumption of tasks submitted to a Java ThreadPoolExecutor using execute and submit, compares lambda and queue implementations, and quantifies the overhead of internal objects such as Worker, Node, FutureTask, and RunnableAdapter.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Memory Overhead of execute vs submit in Java ThreadPoolExecutor

We start by showing a typical usage of ThreadPoolExecutor.execute that submits 200,000 empty tasks to a pool with eight core threads and a LinkedBlockingQueue . The code creates a lambda for each task, which, after pointer compression, occupies 16 bytes (12‑byte object header + 4‑byte padding).

When the queue is a LinkedBlockingQueue , each task is wrapped in a Node object. A Node consists of a 12‑byte header, a 4‑byte reference to the item, and a 4‑byte reference to the next node, plus 4 bytes of padding, totaling 24 bytes. Therefore, a single task submitted via execute with this queue consumes 40 bytes (16 bytes for the lambda + 24 bytes for the node), leading to roughly 7.6 MiB for 200,000 tasks.

If an ArrayBlockingQueue is used instead, the queue does not allocate a Node , so only the lambda remains, reducing the memory to about 3.05 MiB for the same number of tasks.

The execute implementation (simplified) is:

public void execute(Runnable command) {
    if (command == null) throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true)) return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (!isRunning(recheck) && remove(command)) reject(command);
        else if (workerCountOf(recheck) == 0) addWorker(null, false);
    } else if (!addWorker(command, false)) {
        reject(command);
    }
}

The submit path creates additional wrapper objects. It first calls newTaskFor to produce a FutureTask , which in turn creates a RunnableAdapter that holds the original runnable and a result value. The relevant snippets are:

public Future
submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture
ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}
protected
RunnableFuture
newTaskFor(Runnable runnable, T value) {
    return new FutureTask
(runnable, value);
}
public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;
}
public static
Callable
callable(Runnable task, T result) {
    if (task == null) throw new NullPointerException();
    return new RunnableAdapter
(task, result);
}
static final class RunnableAdapter
implements Callable
{
    final Runnable task;
    final T result;
    RunnableAdapter(Runnable task, T result) { this.task = task; this.result = result; }
    public T call() { task.run(); return result; }
}

A FutureTask object occupies 32 bytes (12‑byte header + 4 bytes each for callable, outcome, runner, waiters, plus padding). The accompanying RunnableAdapter adds another 24 bytes. Consequently, a task submitted via submit to a LinkedBlockingQueue consumes about 96 bytes, resulting in roughly 18.3 MiB for 200,000 tasks.

When the lambda does not capture any surrounding variables, the JVM may reuse a single lambda instance for all submissions, reducing the per‑task overhead dramatically. Combining this with an ArrayBlockingQueue and the execute method yields an O(1) space complexity for large numbers of identical tasks.

In summary, the memory footprint of a task depends on the submission method (execute vs. submit), the queue implementation (LinkedBlockingQueue vs. ArrayBlockingQueue), and whether the lambda captures external state. Using execute with an ArrayBlockingQueue and stateless lambdas provides the smallest memory usage.

JavaConcurrencylambdaMemoryThreadPoolExecutorexecutesubmit
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.