ThreadLocal variables are a boon when we develop concurrency-critical applications. They save a lot of coding, and make the application less prone to locking issues.
They do come with a warning, though. If they’re not used correctly, they can result in difficult-to-diagnose memory leaks.
In this article, we’ll look at what ThreadLocal variables are used for, the circumstances that can lead to them going wrong, and how to avoid a ThreadLocal memory leak.
What Is a Memory Leak?
In the JVM, the garbage collector (GC) is a background process that works continually behind the scenes to clear items that are no longer needed from memory. This only works if the items are freed for GC, which happens:
- When they go out of scope;
- When they are specifically released by being set to null.
They can only be cleared if no live objects hold a reference to them.
Memory leaks happen when objects aren’t released for GC, and build up in memory over time. Eventually, they cause performance problems and OutOfMemoryErrors.
To learn more about memory leaks, see this article: The Definitive Guide to Causes, Detection & Fixes of Java Memory Leaks.
What Are ThreadLocal Variables?
Most of us are familiar with three types of scope in Java:
- Local Variables: Defined within a block of code, visible within that block and eligible for GC when the block completes.
- Instance (Member) Variables: Defined outside any code block, visible from anywhere within the same class, and eligible for GC when the object is removed.
- Static (Class) variables: Defined with the keyword static, visible within the class and eligible for GC when the class is unloaded.
ThreadLocal variables are assigned a different scope: they are visible anywhere within the current thread. This is particularly useful for storing context, such as the transaction ID and user ID, for a multi-threaded online transaction server.
They’re usually defined as static(class) variables, although they can be instance(member) variables. The definition statement may look like this:
private static final ThreadLocal<String> transactionID = new ThreadLocal<>();
It’s also possible to define them with an initial value.This doesn’t actually create the variable itself. What happens is that every thread within the class has a ThreadLocal map, containing keys and values. The above statement makes available the key transactionID in every thread. No variable is actually created until the thread issues either a get() or a put() statement on the ThreadLocal:
transactionId.put(getNextID());
This creates and assigns a value to this thread’s copy of transactionID. This value is unique to the thread, and can’t interfere with the value held in other threads.
ThreadLocals are used in frameworks like Log4J and Hibernate. Log4J includes a context class that we can include in our classes:
import org.apache.logging.log4j.ThreadContext;
This class stores context as ThreadLocals, so each thread has its own unique copy. It includes various put() methods so we can set up the values for the current thread:
ThreadContext.put("requestId", getNextID());
The logger can then access the current thread’s transaction ID in order to display it on each log entry. Here, we’re not working directly with ThreadLocals, but making use of the ones defined within the ThreadContext class.
When we’ve finished using these variables, it’s important to call ThreadContext’s clearAll() method, which removes them.
ThreadContext.clearAll();
The thread’s copy of all ThreadLocal variables is not eligible for garbage collection until:
- The thread terminates OR
- The application calls the variable’s remove() method e.g.:
transactionID.remove();
How Do ThreadLocal Memory Leaks Occur?
If we don’t use this facility carefully, it can easily cause a memory leak, especially if the ThreadLocal variables either contain, or retain references to, large variables such as arrays, collections, buffers and images. Ideally, they should only be used for small amounts of data such as transaction IDs.
Leaks occur when:
- A thread doesn’t terminate as expected. This could be caused by a buggy program loop, or if the thread is waiting for a lock or a resource.
- We use ThreadLocals in a thread pool, and don’t remove these variables before handing the thread back to the pool.
- A buggy loop creates a large number of threads that use this facility.
For more information and a sample program that illustrates a ThreadLocal memory leak, see this article: Memory Leak Due To Uncleared ThreadLocal.
How Can We Prevent ThreadLocal Memory Leaks?
The most important strategy is to make sure ThreadLocal variables are always cleared when we no longer need them. The following code snippet is a good pattern to use, where tl is the name of a ThreadLocal variable:
try { tl.set(value); doWork(); } finally { tl.remove(); }
This ensures that even if the code throws an exception, the ThreadLocal will always be removed.
If we’re using thread pools, this is really important. Another good tactic with thread pools is to use the ThreadPoolExecutor class to create the threads, and always implement the afterExecute() facility to remove all ThreadLocal variables. Of course, this may not help if the thread has hung.
Where possible, make use of timeouts for anything that can hang, for example, database and network requests, and locks. We can also put a timeout on the thread itself.
Try not to use ThreadLocals for large variables, or variables such as collections that can potentially grow. Also, take care that a ThreadLocal doesn’t hold references to large objects.
In passing, it’s also worth mentioning that using ThreadLocal with thread pools can cause a different kind of bug: values from the previous user of the thread may be left behind, and cause unpredictable results. This is another reason to make sure these variables are removed.
Conclusion
This type of variable is extremely useful, but always be aware of the danger of ThreadLocal memory leaks.
Guard against uncleared variables, rogue threads that don’t terminate, and hung threads.
Using the remove() method of the variable is the best solution. This can be included in a finally block, or in an afterExecute() method when using the ThreadPoolExecutor class.

Share your Thoughts!