Why ThreadLocal Can Still Leak Memory: Understanding Weak References and GC in Java
This article explains the inner workings of ThreadLocal, the relationships among Thread, ThreadLocalMap, Entry, and various reference types, demonstrates how weak references behave during garbage collection, and shows practical code examples and reference‑queue usage to avoid memory‑leak pitfalls.
1. Quick Review
Previously we introduced how improper use of ThreadLocal can cause OOM and the basic principles of ThreadLocal. Below is a recap of the ThreadLocal architecture diagram and the relationships among the involved classes.
1) Relationship among Thread, ThreadLocal, ThreadLocalMap, and Entry (Diagram A):
A thread contains a single ThreadLocalMap; a ThreadLocalMap can hold many ThreadLocal objects, each mapping to an Entry entity. Thus a thread may be associated with multiple ThreadLocal objects.
2) Reference relationships in ThreadLocal (Diagram B):
During the lifecycle of ThreadLocal, both strong and weak references exist (solid lines = strong, dashed lines = weak). If the key reference to an Entry is broken and the Entry is not cleaned promptly, an OOM may occur.
2. Types of References
In Java, references are classified into four categories whose strength decreases progressively: Strong, Soft, Weak, and Phantom references.
1) Strong Reference : Typical object references such as Object obj = new Object();. As long as a strong reference exists, the garbage collector will never reclaim the object, potentially leading to OOM when memory is insufficient.
2) Soft Reference : Used for objects that are useful but not essential. The GC reclaims softly reachable objects only when memory is about to run out, making them suitable for implementing caches (e.g., database cache). Implemented via SoftReference.
3) Weak Reference : Weaker than soft references; objects reachable only through weak references are reclaimed at the next GC cycle, regardless of memory pressure. Implemented via WeakReference. ThreadLocal uses weak references for its keys.
4) Phantom (Virtual) Reference : The weakest reference type; it does not prevent reclamation and cannot retrieve the referent. It is used solely to receive a notification after the object is collected, implemented via PhantomReference.
Lifecycle diagram of reference types:
3. Weak References in ThreadLocal
The key of each Entry in ThreadLocalMap is a weak reference. When GC runs, these keys may be cleared, turning the Entry into a key‑null element.
Demo case: Multiple ThreadLocal variables are created in the main thread, GC is triggered several times, and the changes in ThreadLocalMap are observed.
Normal execution: After the loop finishes, the map contains 21 entries (including 3 from the main thread) and no GC has reclaimed any keys.
Forced GC execution: By manually invoking GC via JConsole during the loop, the map shrinks to 6 entries, proving that weak‑referenced keys have been reclaimed.
ThreadLocal’s get(), set(), and remove() methods automatically clean up entries whose keys have become null.
In normal usage, ThreadLocal does not cause OOM; OOM occurs only when entries are not cleaned properly.
Additional experiment: Adjusting VM parameters and running the program shows that only 14 of the 30 defined ThreadLocal variables remain after GC, and two specific variables (threadLocal1 and threadLocal2) retain their data because their keys were not reclaimed.
When the strong reference to an object is removed (e.g., user = null), only the weak reference remains, and the GC will finally reclaim the object.
Thus, weak references are only reclaimed when they are the sole references to an object.
4. Reference Queues
Reference queues allow programs to be notified when the reachability of an object changes. When a SoftReference (or WeakReference/PhantomReference) is cleared, the JVM enqueues the reference object into the associated queue.
By polling the queue, one can detect which references have been reclaimed and perform cleanup actions, such as removing cache entries.
Example code (image) demonstrates creating a ReferenceQueue, attaching it to a SoftReference, and observing the queue after GC.
The same principle is used inside MyBatis to manage cache entries with weak and soft references.
Understanding these mechanisms helps avoid memory‑leak bugs and use references correctly in Java applications.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
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.
