0

According to the documentation of ThreadLocal (https://docs.oracle.com/javase/6/docs/api/java/lang/ThreadLocal.html)

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).

But what if this "implicit reference" is the only source of accessibility of this ThreadLocal? Consider the following class:

public class ThreadLocalBigObjectSupplier implements Supplier<BigObject> {

private final ThreadLocal<BigObject> threadLocalBigObject = ThreadLocal.withInitial(() -> new BigObject(this));

@Override
public BigObject get()
{
    final BigObject bigObject = threadLocalBigObject.get();
    return bigObject;
}}

It is not so important what BigObject is, but what matters is that bigObject instance stores reference to this and, as a result, threadLocalBigObject is accessible via bigObject -> this -> threadLocalBigObject. Let's see what happens if we create a many copies of ThreadLocalBigObjectSupplier

public class MemoryLeakTest {
public static void main(String[] args) {
    final ExecutorService executorService = Executors.newSingleThreadExecutor();
    while (true)
    {
        final Supplier<BigObject> supplier = new BigObjectSupplier();
        executorService.execute(supplier::get);
    }
}}

It turns out that this code fails with OOM quite fast (depending on your BigObject size) because executorService thread stores reference to the bigObject instances and there are no other references. From heap dump on OOM:

enter image description here

Is it the expected behavior of ThreadLocal in such cases? What are the standard ways / best practices to avoid this kind of memory leaks?


Additional details and environment:

public class BigObject {
    private final double[] array = new double[123_456];
    private final Object ref;

    public BigObject(Object ref) {
        this.ref = ref;
    }}

MacOS BigSur 11.2.2, opendjdk 11.0.11, default GC

stepan2271
  • 183
  • 4
  • 2
    Yes, backreferences to the thread local prevent its garbage collection. Normally, you don’t store objects with back-references, but see [How does this ThreadLocal prevent the Classloader from getting GCed](https://stackoverflow.com/q/66452567/2711488) for a practical example where even using the correct usage pattern can’t prevent the memory leak. – Holger Nov 17 '21 at 08:57

0 Answers0