4

I have a threadlocal object which is initialized with an object of a non static inner class, like this:

public class StressTestThreadLocal {

  private final ThreadLocal<TObject> tObjectThreadLocal = ThreadLocal.withInitial(
      () -> new TObject(1000));

  private static ExecutorService executorService = Executors.newFixedThreadPool(4);

  private void startThread() {
    executorService.submit(tObjectThreadLocal::get);
  }

  public class TObject {
    List<Integer> test;

    TObject(int n) {
      test = new ArrayList<>();
      for (int i = 0; i < n; i++) {
        test.add(i);
      }
      System.out.println("Done making TObject " + UUID.randomUUID());
    }
  }

  public static void main(String[] args) {
    for (int i = 0; i < 100000; i++) {
      StressTestThreadLocal testThreadLocal = new StressTestThreadLocal();
      testThreadLocal.startThread();
    }
    while (true) {
      try {
        Thread.sleep(10000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}

I ran this program, attached jconsole, triggerred GC multiple times but the memory usage did not go down. I then took a heapdump and analysed the number of objects created of TObject class. It showed that all 100,000 objects were available in the memory.

Screenshot of the heapdump, check out the object count

I made the inner class static meaning that it is no longer strongly referencing the outer class object and ran the same code again. Here triggering the GC brought down the memory usage significantly and the number of objects in the memory were only around 3000.

Screenshot of the heapdump with only 3000 objects

What I am not sure I understand is this:

In first case, the outerObject and the innerObject are holding strong references to each other but none of them are strongly referenced from anywhere else. If threadlocalmap only contains a weak reference to the threadlocal variable (TObject) and we did not save the reference to the outer object StressTestThreadLocal anywhere else, why was the threadlocal object not eligible for garbage collection? And why did making the inner class static automatically solve this problem?

GhostCat
  • 137,827
  • 25
  • 176
  • 248
Manyata Goyal
  • 604
  • 5
  • 13

1 Answers1

6

Thread local map has a strong reference to the actual value (which is TObject in this case). Only the key of the map (ThreadLocal) is a weak reference. The reason behind making the key a weak reference is to expunge the entry from the map when the key is no longer referenced. http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/lang/ThreadLocal.java?av=f#298

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v; 
        }
    }

When the inner class is non-static, the object has a strong ref to StressTestThreadLocal which in turn has a strong ref to tObjectThreadLocal (which is the key in thread local map). So the entry never gets garbage collected.

TLM -> TLM.Entry.Value (TObject) -> StressTestThreadLocal -> TLM.Entry.Key (ThreadLocal)
where TLM = Thread local map

Rohan Laishram
  • 198
  • 1
  • 6