如果线程局部映射包含对线程局部对象的弱引用,那么为什么它没有被垃圾回收?
If thread local map contains a weak reference to the threadlocal object, then why is it not garbage collected?
我有一个线程局部对象,它是用一个非静态内部 class 的对象初始化的,像这样:
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();
}
}
}
}
我运行这个程序,附加jconsole,多次触发GC但内存使用率没有下降。然后我进行了 heapdump 并分析了 TObject
class 创建的对象数量。它显示内存中所有 100,000 个对象都可用。
Screenshot of the heapdump, check out the object count
我将内部 class 设为静态,这意味着它不再强烈引用外部 class 对象和 运行 相同的代码。这里触发GC,内存占用明显下降,内存中的对象数只有3000左右
Screenshot of the heapdump with only 3000 objects
我不确定我理解的是:
在第一种情况下,outerObject 和 innerObject 相互持有强引用,但其中 none 是从其他任何地方强引用的。如果 threadlocalmap 仅包含对 threadlocal 变量 (TObject
) 的弱引用,而我们没有在其他任何地方保存对外部对象 StressTestThreadLocal
的引用,为什么 threadlocal 对象不符合垃圾回收条件?为什么使内部 class static 自动解决了这个问题?
线程本地映射具有对实际值(在本例中为 TObject)的强引用。只有映射的键(ThreadLocal)是弱引用。使键成为弱引用的原因是当键不再被引用时从映射中删除条目。
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;
}
}
当内部 class 为非静态时,对象具有对 StressTestThreadLocal 的强引用,而后者又具有对 tObjectThreadLocal(线程局部映射中的键)的强引用。所以条目永远不会被垃圾收集。
TLM -> TLM.Entry.Value (TObject) -> StressTestThreadLocal -> TLM.Entry.Key (ThreadLocal)
其中 TLM = 线程本地映射
我有一个线程局部对象,它是用一个非静态内部 class 的对象初始化的,像这样:
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();
}
}
}
}
我运行这个程序,附加jconsole,多次触发GC但内存使用率没有下降。然后我进行了 heapdump 并分析了 TObject
class 创建的对象数量。它显示内存中所有 100,000 个对象都可用。
Screenshot of the heapdump, check out the object count
我将内部 class 设为静态,这意味着它不再强烈引用外部 class 对象和 运行 相同的代码。这里触发GC,内存占用明显下降,内存中的对象数只有3000左右
Screenshot of the heapdump with only 3000 objects
我不确定我理解的是:
在第一种情况下,outerObject 和 innerObject 相互持有强引用,但其中 none 是从其他任何地方强引用的。如果 threadlocalmap 仅包含对 threadlocal 变量 (TObject
) 的弱引用,而我们没有在其他任何地方保存对外部对象 StressTestThreadLocal
的引用,为什么 threadlocal 对象不符合垃圾回收条件?为什么使内部 class static 自动解决了这个问题?
线程本地映射具有对实际值(在本例中为 TObject)的强引用。只有映射的键(ThreadLocal)是弱引用。使键成为弱引用的原因是当键不再被引用时从映射中删除条目。 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;
}
}
当内部 class 为非静态时,对象具有对 StressTestThreadLocal 的强引用,而后者又具有对 tObjectThreadLocal(线程局部映射中的键)的强引用。所以条目永远不会被垃圾收集。
TLM -> TLM.Entry.Value (TObject) -> StressTestThreadLocal -> TLM.Entry.Key (ThreadLocal)
其中 TLM = 线程本地映射