21

From javadoc

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

from that it seems that objects referenced by a ThreadLocal variable are garbage collected only when thread dies. But what if ThreadLocal variable a is no more referenced and is subject for garbage collection? Will object references only by variable a be subject to garbage collection if thread that holds a is still alive?

for example there is following class with ThreadLocal variable:

public class Test {
    private static final ThreadLocal a = ...; // references object b
}

This class references some object and this object has no other references to it. Then during context undeploy application classloader becomes a subject for garbage collection, but thread is from a thread pool so it does not die. Will object b be subject for garbage collection?

michael nesterenko
  • 14,222
  • 25
  • 114
  • 182

6 Answers6

10

TL;DR : You cannot count on the value of a ThreadLocal being garbage collected when the ThreadLocal object is no longer referenced. You have to call ThreadLocal.remove or cause the thread to terminate

(Thanks to @Lii)


Detailed answer:

from that it seems that objects referenced by a ThreadLocal variable are garbage collected only when thread dies.

That is an over-simplification. What it actually says is two things:

  • The value of the variable won't be garbage collected while the thread is alive (hasn't terminated), AND the ThreadLocal object is strongly reachable.

  • The value will be subject to normal garbage collection rules when the thread terminates.

There is an important third case where the thread is still live but the ThreadLocal is no longer strongly reachable. That is not covered by the javadoc. Thus, the GC behavior in that case is unspecified, and could potentially be different across different Java implementations.

In fact, for OpenJDK Java 6 through OpenJDK Java 17 (and other implementations derived from those code-bases) the actual behavior is rather complicated. The values of a thread's thread-locals are held in a ThreadLocalMap object. The comments say this:

ThreadLocalMap is a customized hash map suitable only for maintaining thread local values. [...] To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys. However, since reference queues are not used, stale entries are guaranteed to be removed only when the table starts running out of space.

If you look at the code, stale map entries (with broken WeakReferences) may also be removed in other circumstances. If stale entry is encountered in a get, set, insert or remove operation on the map, the corresponding value is nulled. In some cases, the code does a partial scan heuristic, but the only situation where we can guarantee that all stale map entries are removed is when the hash table is resized (grows).


So ...

Then during context undeploy application classloader becomes a subject for garbage collection, but thread is from a thread pool so it does not die. Will object b be subject for garbage collection?

The best we can say is that it may be ... depending on how the application manages other thread locals the thread in question.

So yes, stale thread-local map entries could be a storage leak if you redeploy a webapp, unless the web container destroys and recreates all of the request threads in the thread pool. (You would hope that a web container would / could do that, but AFAIK it is not specified.)

The other alternative is to have your webapp's servlets always clean up after themselves by calling ThreadLocal.remove on each one on completion (successful or otherwise) of each request.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • 2
    This is a nice explanation of the issue. I'd like to add a condensed version of the conclusion to take away from this: **You can not count on the value of a `ThreadLocal` being garbage collected when the `ThreadLocal` object is no longer referenced. You have to call `ThreadLocal.remove` or terminate the thread.** – Lii Jan 03 '15 at 12:29
8

ThreadLocal variables are hold in Thread

ThreadLocal.ThreadLocalMap threadLocals;

which is initialized lazily on first ThreadLocal.set/get invocation in the current thread and holds reference to the map until Thread is alive. However ThreadLocalMap uses WeakReferences for keys so its entries may be removed when ThreadLocal is referenced from nowhere else. See ThreadLocal.ThreadLocalMap javadoc for details

ayushgp
  • 4,891
  • 8
  • 40
  • 75
Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
  • 4
    This answer is unclear. "_so its entries may be removed_" The most important part for the question is: When will they actually be removed? It's not only you that are unclear on this point, both the documentation and the whole of Stack Overflow seem equally confused. – Lii Dec 12 '14 at 12:21
  • "When will they actually be removed?" The correct answer is nobody knows. This is an implementation detail of JVM. For particular GC it might never happens, e.g. Epsilon GC on HotSpot. – glee8e Aug 28 '21 at 16:45
7

If the ThreadLocal itself is collected because it's not accessible anymore (there's an "and" in the quote), then all its content can eventually be collected, depending on whether it's also referenced somewhere else and other ThreadLocal manipulations happen on the same thread, triggering the removal of stale entries (see for example the replaceStaleEntry or expungeStaleEntry methods in ThreadLocalMap). The ThreadLocal is not (strongly) referenced by the threads, it references the threads: think of ThreadLocal<T> as a WeakHashMap<Thread, T>.

In your example, if the classloader is collected, it will unload the Test class as well (unless you have a memory leak), and the ThreadLocal a will be collected.

Frank Pavageau
  • 11,477
  • 1
  • 43
  • 53
  • 1
    "_The ThreadLocal is not referenced by the threads_" This is actually not the case, `ThreadLocal` is implemented so that each `Thread` object holds a reference to a map of `ThreadLocal`s. – Lii Dec 12 '14 at 12:25
  • "_If the ThreadLocal itself is collected because it's not accessible anymore (there's an "and" in the quote), then all its content can also be collected_" Can you give any source for this claim? The documentation is most unclear, and several other SO answers seem to at least suggest otherwise. – Lii Dec 12 '14 at 12:27
  • The `ThreadLocal` is not _strongly_ referenced by the thread either (I'll update the answer). See the [implementation of `Entry`](http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/tip/src/share/classes/java/lang/ThreadLocal.java#l271) in OpenJDK, which extends `WeakReference`. Hence, nothing prevents garbage collection. – Frank Pavageau Dec 13 '14 at 13:22
  • 1
    But note that the _value_ in the `ThreadLocal` will not be collected! At least not immediately. There is a strong reference to the value. From what I can tell from the source code the value might or might not be collected later, depending on whether the `TheadLocal` decides to rehash. – Lii Dec 13 '14 at 22:33
  • As soon as the `ThreadLocal` becomes unreachable, it can be collected and so can the values it contains if they are not referenced from anywhere else. The garbage collector collects entire unreachable object graphs at once, even if some parts of the graph strongly reference some others. What matters is that they're not reachable from roots. Also, the `ThreadLocal` won't rehash if it's unreachable, noone can manipulate it. – Frank Pavageau Dec 14 '14 at 14:31
  • 1
    But the value(s) in the `ThreadLocal` aren't actually stored in the `ThreadLocal` object but in a map in the thread object. `Thread` has a hard reference to a `ThreadLocalMap`, which has a hard reference to an `Entry`, which has a hard reference to the value. The value is reachable as long as the thread lives, even if the `ThreadLocal` object has become unreachable. – Lii Dec 15 '14 at 09:48
  • 2
    The `ThreadLocalMap` is indeed stored in the thread, but it can expunge stale entries (see [line 556 in `expungeStaleEntry`](http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/4728d748209d/src/share/classes/java/lang/ThreadLocal.java#l556) or [line 532 in `replaceStaleEntry`](http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/4728d748209d/src/share/classes/java/lang/ThreadLocal.java#l532)). OK, so strictly speaking the values are not *immediately* collectable and I'll again update the answer, but still, they *can* be collected *before the thread dies* under the right circumstances. – Frank Pavageau Dec 16 '14 at 22:02
3

ThreadLocal contains a reference to a WeakHashMap that holds key-value pairs

enter image description here

anish
  • 6,884
  • 13
  • 74
  • 140
0

It depends, it will not be garbage collected if your are referencing it as static or by singleton and your class is not unloaded, that is why in application server environment and with ThreadLocal values, you have to use some listener or request filter the be sure that you are dereferencing all thread local variables at the end of the request processing. Or either use some Request scope functionality of your framework.

You can look here for some other explanations.

EDIT: In the context of a thread pool as asked, of course if the Thread is garbaged thread locals are.

Community
  • 1
  • 1
gma
  • 2,563
  • 15
  • 14
-1

Object b will not be subject for garbage collection if it somehow refers to your Test class. It can happen without your intention. For example if you have a code like this:

public class Test {
    private static final ThreadLocal<Set<Integer>> a =
     new ThreadLocal<Set<Integer>>(){
            @Override public Set<Integer> initialValue(){
                return new HashSet<Integer>(){{add(5);}};
            }
    };
}

The double brace initialization {{add(5);}} will create an anonymous class which refers to your Test class so this object will never be garbage collected even if you don't have reference to your Test class anymore. If that Test class is used in a web app then it will refer to its class loader which will prevent all other classes to be GCed.

Moreover, if your b object is a simple object it will not be immediately subject for GC. Only when ThreadLocal.ThreadLocalMap in Thread class is resized you will have your object b subject for GC.

However I created a solution for this problem so when you redeploy your web app you will never have class loader leaks.

user1944408
  • 509
  • 3
  • 12