[英]Whether ThreadLocal value is GCed in Java ThreadPool?
In my code, I want to test ThreadLocal's GC strategy. 在我的代码中,我想测试ThreadLocal的GC策略。 I use two methods.
我使用两种方法。 One is
ThreadPool
, the other is a self-created thread. 一个是
ThreadPool
,另一个是自创建的线程。 In the first scenarios, JVM
doesn't GC Thread's ThreadLocalMap
seemly(No finalize()
output). 在第一种情况下,
JVM
似乎没有GC Thread的ThreadLocalMap
(没有finalize()
输出)。 The other works well. 另一个效果很好。
I've found. 我找到了 In October 2007, Josh Bloch (co-author of java.lang.ThreadLocal along with Doug Lea) wrote:
2007年10月,Josh Bloch(java.lang.ThreadLocal和Doug Lea的合著者)写道:
"The use of thread pools demands extreme care. Sloppy use of thread pools in combination with sloppy use of thread locals can cause unintended object retention, as has been noted in many places."
“使用线程池需要格外小心。草率使用线程池与草率使用线程本地变量相结合会导致意外的对象保留,这在许多地方都已提到。”
I guess ThreadPool
may be dangerous to use ThreadLocal
. 我猜想
ThreadPool
使用ThreadLocal
可能很危险。
Here is my code(JDK8 environment) 这是我的代码(JDK8环境)
public class ThreadLocalDemo_Gc {
static volatile ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>(){
// overwrite finalize, such that the message will be printed when GC happens.
protected void finalize() throws Throwable{
System.out.println(this.toString() + " is gc(threadlocal)");
}
};
// Let the main thread wait for all workers.
static volatile CountDownLatch cd = new CountDownLatch(10);
public static class ParseDate implements Runnable{
int i = 0;
public ParseDate(int i) {
super();
this.i = i;
}
@Override
public void run() {
try {
if(tl.get() == null){
tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"){
// overwrite finalize, such that the message will be printed when GC happens.
protected void finalize() throws Throwable {
System.out.println(this.toString() + " is gc(sdf)");
}
});
// new sdf object is created in ThreadLocalMap
System.out.println(Thread.currentThread().getId() + ":create SimpleDateFormat");
}
Date t = tl.get().parse("2017-3-26 17:03:" + i % 60);
} catch (ParseException e) {
e.printStackTrace();
} finally {
cd.countDown();
}
}
}
// code with ThreadPool
// public static void main(String[] args) throws InterruptedException {
// ExecutorService es = Executors.newFixedThreadPool(10);
//
// for(int i = 0; i < 10; i++){
// es.execute(new ParseDate(i));
// }
// cd.await();
//
// System.out.println("mission complete");
//
// tl = null; // free the weak reference
// System.gc();
// System.out.println("First GC complete");
// es.shutdown();
// }
// not pooling threads
public static void main(String[] args) throws InterruptedException {
Thread[] all = new Thread[10];
for(int i = 0; i < 10; i++){
all[i] = new Thread(new ParseDate(i));
}
for(int i = 0; i < 10; i++){
all[i].start();
}
cd.await();
tl = null;
System.gc();
System.out.println("First GC complete");
}
}
After running the first main()
function. 运行第一个
main()
函数之后。 None of the SimpleDateFormat
object is GCed. 没有任何
SimpleDateFormat
对象被GC。 The second main()
function indeed does that job. 第二个
main()
函数确实可以完成这项工作。
Edit #1 Thanks to Gray's remind. 编辑#1感谢Gray的提醒。 The real problem, which results in no output in the function
finalize()
, is the ThreadPool
may not be truly collected. 真正的问题是,可能未真正收集
ThreadPool
,导致函数finalize()
中没有输出。 In the test code, only shutdown()
was used. 在测试代码中,仅使用
shutdown()
。 However, the worker threads may not be collected after this process. 但是,此过程之后可能无法收集工作线程。 So more safer way is invoking
awaitTermination()
. 因此,更安全的方法是调用
awaitTermination()
。 This function does generate all worker threads instance, and the resource those belongs to is collected, spefically ThreadLocalMap
. 此函数确实生成所有工作线程实例,并且收集属于它们的资源,特别是
ThreadLocalMap
。
Here is the revision of the main()
with ThreadPool
这是
ThreadPool
对main()
的修改
// code with ThreadPool
public static void main(String[] args) throws InterruptedException {
ExecutorService es = Executors.newFixedThreadPool(10);
for(int i = 0; i < 10; i++){
es.execute(new ParseDate(i));
}
cd.await();
es.shutdown();
es.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
System.gc();
}
This version of main()
works well, all the collection message from finalize()
method are printed. 此版本的
main()
效果很好,所有来自finalize()
方法的收集消息都将被打印出来。
Finally, Java GC may not collect the value when the instance of Entry
's key has no stable reference. 最后,当
Entry
的键的实例没有稳定的引用时,Java GC可能不会收集该值 。 As ThreadLocalMap
's key is the weak reference, the Entry
's key becomes null
. 由于
ThreadLocalMap
的键是弱引用,因此Entry
的键变为null
。 However, the Entry
's value is not GCed. 但是,
Entry
的值不被GCed。 This conclusion may be proved in my test. 这个结论可以在我的测试中得到证明。
Instances of ThreadLocal
itself are merely a view into a map stored on the thread itself. ThreadLocal
本身的实例仅仅是存储在线程本身上的映射视图。 The instance being collected does not actually guarantee that the reference is severed. 所收集的实例实际上并不能保证参考被切断。
It can be approximated as threadInstance.privateField = WeakHashMap<ThreadLocal<T>,T>
. 可以近似为
threadInstance.privateField = WeakHashMap<ThreadLocal<T>,T>
。
That means if the Thread
instance becomes unreachable so become all associated values held by ThreadLocal
. 这意味着如果
Thread
实例变得不可访问,那么将成为ThreadLocal
持有的所有关联值。 On the other hand when the ThreadLocal
instance becomes unreachable that only means the map key is nulled (being a weak reference), the value is still held alive by the map until some accesses to the map clean the value.The map cleaning is performed lazily, so cleaning up ThreadLocal
references does not have the same effect as letting threads terminate. 另一方面,当
ThreadLocal
实例变得不可访问时,这仅意味着映射键为空(作为弱引用),该值仍保持为活动状态,直到对地图的某些访问清除了该值为止。 ,因此清理ThreadLocal
引用与让线程终止不会产生相同的效果。 The third way of cleaning it is calling threadLocal.remove()
from within the thread. 清除它的第三种方法是从线程内部调用
threadLocal.remove()
。
And of course it's a common pattern to have shared static final ThreadLocal<T> tl
accessors within a class. 当然,在类中共享
static final ThreadLocal<T> tl
访问器是一种常见的模式。 When combined with a thread pool that means those values will stay alive as long as the thread pool does unless you use remove()
当与线程池结合使用时,这意味着这些值将与线程池一样有效,除非您使用
remove()
I guess ThreadPool may be dangerous to use ThreadLocal.
我猜想ThreadPool使用ThreadLocal可能很危险。
I wouldn't go this far. 我不会走那么远。 I would say that you need to take into account that the
ThreadLocal
storage won't be reaped unless the thread itself terminates. 我要说的是,您需要考虑到除非线程本身终止,否则不会收获
ThreadLocal
存储。
But in looking at your test code, there are a lot of problems with both the ExecutorService
and direct thread main methods. 但是在查看测试代码时,
ExecutorService
和直接线程主方法都存在很多问题。 In both cases you are not properly joining with the completed threads. 在这两种情况下,您都无法正确地使用已完成的线程。 Ditch the
CountDownLatch
and do the following before the gc()
call: 放弃
CountDownLatch
并在gc()
调用之前执行以下操作:
for (int i = 0; i < 10; i++) {
all[i].join();
}
or 要么
es.shutdown();
es.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
But the real problem with your code is that you have a race condition with the Finalizer thread. 但是代码的真正问题在于,终结器线程具有竞争条件。 The gc thread finishes but the actually finalizing of the objects happens in another "Finalizer" thread after the GC completed.
gc线程完成了,但是对象的最终确定发生在GC完成之后的另一个“ Finalizer”线程中。 If you just put a 1 second sleep at the end of the
main()
you should see all 10 SDFs reaped. 如果仅在
main()
的末尾放置1秒钟的睡眠,您应该会看到所有10个SDF都已收获。
What this demonstrates really is that it is hard to force objects to the GC'd in such a way. 这实际上表明,很难以这种方式将对象强制发送到GC。 Putting
System.out.println(...)
commands in a finalizer()
gives me the chills even thinking about it even though I know you are doing it to learn more about ThreadLocal
's memory usage. 将
System.out.println(...)
命令放在finalizer()
,即使我知道要了解更多有关ThreadLocal
的内存使用情况,也正在思考这个问题。
I think that storing things in ThreadLocal
s if done carefully shouldn't be a problem. 我认为,如果仔细完成,将事情存储在
ThreadLocal
应该不是问题。 In your thread's run method, I would just do a try / finally
block and make sure to do a threadLocal.remove()
in the finally
so the thread cleans itself up before exiting. 在您线程的run方法中,我只需要执行
try / finally
块,并确保在finally
执行一个threadLocal.remove()
,以便线程在退出之前清理自身。 But I don't even bother with that if I have a background thread which is running for the life of my application. 但是,如果我有一个在我的应用程序生命周期中一直运行的后台线程,我什至不理会。 It is really only threads that come and go that you need to be particularly worried about.
实际上,只有特别多的线程需要您特别担心。
Lastly, there is no need for a ThreadLocal
field to be volatile
and it should be a static
within the ParseDate
if possible. 最后,不需要
ThreadLocal
字段是volatile
并且如果可能的话,它在ParseDate
应该是static
的。
Hope this helps. 希望这可以帮助。
A Thread
in a ThreadPool
may never terminate until the ThreadPool
does. ThreadPool
的Thread
可能永远不会终止,直到ThreadPool
终止。 That's the whole point of ThreadPool
. 这就是
ThreadPool
。 So it never gets GC'd. 因此,它永远不会得到GC。 So of course the
Thread
's ThreadLocal
doesn't get GC'd either. 因此,当然,
Thread
的ThreadLocal
也不会得到GC。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.