ThreadLocal的误解
程序员文章站
2022-03-02 18:35:43
...
可能大部分人的想法和我当初的想法一样,都是以为在ThreadLocal里使用一个Map,这个Map的键为Thread,值为绑定的变量。其实如果这样做是有问题的:
1. 就是当线程回收时,该线程绑定的变量不能被自动的回收,因为变量存储在ThreadLocal里,必须显式的去回收。如果此变量存储在线程里,那么线程回收时,这个变量没有被其他引用指向的话,它便随着线程一起回收。
2. 另外不这样做还有一个好处:如果Map在ThreadLocal里,那你必须得考虑线程同步访问这个Map,但是这确实没有必要,因为线程访问自己的变量,和其他线程没有直接的关系,所以把Map放在线程里,就不需要做同步的处理,这样即加快了访问的速度。
其实实现不是这样的:每个线程都包含一个ThreadLocal.ThreadLocalMap变量threadLocals(延迟创建的),这个映射(Map)目的就是为每个线程存储关联到使用到的不同ThreadLocal的变量,这个很好理解,因为,一个线程可能使用到多个不同的ThreadLocal对象,每一个ThreadLocal对象的值都被认为是不同的。于是,每次调用ThreadLocal的get()方法,其实就是获取当前线程(Thread.currentThread()),然后从threadLocals映射里,根据ThreadLocal对象,找出其关联的拷贝,这个值便是当前线程的,隔离于其他线程的值。
我们知道,ThreadLocal.ThreadLocalMap映射使用的键是被WeakReference包装的ThreadLocal对象,如果ThreadLocal对象没有其他强引用和软引用指向时,该线程也不会继续持有ThreadLocal对象,因为根据JVM规范,它会被垃圾回收器下次回收时销毁,这一定程度避免了内存泄露,但不表示不会出现内存泄露,关于ThreadLocal引起的内存泄露,特别是导致ClassLoader不能被回收,网上有很多文章都在讨论。在Java 1.5开始,加入了remove()方法,这样我们可以显式的调用此方法,释放内存,所以使用ThreadLocal要特别注意内存泄露的问题。
看来大家对ThreadLocal内存泄露的原因有点误解,其导致Classloader内存泄露的原因在这篇博文写的很清楚了Classloader leaks:,我在这里补充一下,jvm规范下的classloader能被回收的条件是,所有该classloader产生的所有对象都被回收了:我们知道,对象有一个隐式的引用指向它的类型class对象,而class对象有个隐式的引用指向它的classloader,所以如果有一个对象不回收,那么可能导致整个classloader不能够被回收。根据sun的关于Perm回收的文章回应,perm在每次老生代(Tenured/Old generation)收集前回收。但是由于泄露,可能多次导致classloader不能被回收而引发Perm最终泄露。在Java 8里,开始不再使用Perm generation,会和JRockit等一样,使用Meta space放置class对象,这无疑是一个好消息。
特此申明一下,我的书籍http://redhat.iteye.com/admin/blogs/1007884关于4.2.3章节出现了纰漏,当我意识到错误时,花了1-2周来查看此问题,下载了sun jdk1.5版本,1.6版本,1.3版本,1.2版本,以及IBM的jdk代码进行比较,这个纰漏的申明在http://redhat.iteye.com/blog/1057991里,自己置顶,这个理解错误的原因是读取别人(老外)写的博文错误引起的,希望大家能够多读代码,谢谢!
1. 就是当线程回收时,该线程绑定的变量不能被自动的回收,因为变量存储在ThreadLocal里,必须显式的去回收。如果此变量存储在线程里,那么线程回收时,这个变量没有被其他引用指向的话,它便随着线程一起回收。
2. 另外不这样做还有一个好处:如果Map在ThreadLocal里,那你必须得考虑线程同步访问这个Map,但是这确实没有必要,因为线程访问自己的变量,和其他线程没有直接的关系,所以把Map放在线程里,就不需要做同步的处理,这样即加快了访问的速度。
其实实现不是这样的:每个线程都包含一个ThreadLocal.ThreadLocalMap变量threadLocals(延迟创建的),这个映射(Map)目的就是为每个线程存储关联到使用到的不同ThreadLocal的变量,这个很好理解,因为,一个线程可能使用到多个不同的ThreadLocal对象,每一个ThreadLocal对象的值都被认为是不同的。于是,每次调用ThreadLocal的get()方法,其实就是获取当前线程(Thread.currentThread()),然后从threadLocals映射里,根据ThreadLocal对象,找出其关联的拷贝,这个值便是当前线程的,隔离于其他线程的值。
我们知道,ThreadLocal.ThreadLocalMap映射使用的键是被WeakReference包装的ThreadLocal对象,如果ThreadLocal对象没有其他强引用和软引用指向时,该线程也不会继续持有ThreadLocal对象,因为根据JVM规范,它会被垃圾回收器下次回收时销毁,这一定程度避免了内存泄露,但不表示不会出现内存泄露,关于ThreadLocal引起的内存泄露,特别是导致ClassLoader不能被回收,网上有很多文章都在讨论。在Java 1.5开始,加入了remove()方法,这样我们可以显式的调用此方法,释放内存,所以使用ThreadLocal要特别注意内存泄露的问题。
看来大家对ThreadLocal内存泄露的原因有点误解,其导致Classloader内存泄露的原因在这篇博文写的很清楚了Classloader leaks:,我在这里补充一下,jvm规范下的classloader能被回收的条件是,所有该classloader产生的所有对象都被回收了:我们知道,对象有一个隐式的引用指向它的类型class对象,而class对象有个隐式的引用指向它的classloader,所以如果有一个对象不回收,那么可能导致整个classloader不能够被回收。根据sun的关于Perm回收的文章回应,perm在每次老生代(Tenured/Old generation)收集前回收。但是由于泄露,可能多次导致classloader不能被回收而引发Perm最终泄露。在Java 8里,开始不再使用Perm generation,会和JRockit等一样,使用Meta space放置class对象,这无疑是一个好消息。
特此申明一下,我的书籍http://redhat.iteye.com/admin/blogs/1007884关于4.2.3章节出现了纰漏,当我意识到错误时,花了1-2周来查看此问题,下载了sun jdk1.5版本,1.6版本,1.3版本,1.2版本,以及IBM的jdk代码进行比较,这个纰漏的申明在http://redhat.iteye.com/blog/1057991里,自己置顶,这个理解错误的原因是读取别人(老外)写的博文错误引起的,希望大家能够多读代码,谢谢!