A long-lived pooled thread sets a ThreadLocal value and never removes it. The application explicitly nulls every strong reference to the ThreadLocal instance, expecting the WeakReference key to let it all be collected. Why might the large VALUE still survive indefinitely?
static ThreadLocal<byte[]> tl = new ThreadLocal<>();
// on a pooled thread:
tl.set(new byte[50_000_000]);
// later: the field 'tl' is reassigned to a new ThreadLocal
tl = new ThreadLocal<>();