教程视频:https://www.bilibili.com/video/BV1fA411b7SX?from=search&seid=3920039686913857888
从47分钟开始讲threadLocal
强引用
finalize方法
c++转java的程序员有可能会在finalize()中手动释放资源(c++需要手动释放资源,但是java有gc回收机制会自动回收资源),如果在finalize()中写很多释放资源的逻辑,如回收对象、关闭远程连接(需要得到对方反馈)的操作。
我的想法:在内存资源不足时,gc才会开始工作,gc工作时会调用finalize(),如果在finalize()中写很多释放资源的逻辑会导致GC负担更大,程序运行效率更差,甚至会报错,fgc和oom。
@Overrideprotected void finalize() throws Throwable {super.finalize();}
软引用
SoftReference<byte[]> m = new SoftReference<>(new byte[1024*1024*10]);
控制台输出软引用对象
手动调用gc看看软引用是否会被回收
设置JVM初始堆内存只有20m,生成m对象需要10m内存,再生成b对象需要15m内存
如果是再创建一个软引用会怎么样

弱引用

ThreadLocal
原理
可以看一下熬丙的文章:https://www.zhihu.com/question/341005993
ThreadLocalMap的底层用到了弱引用,底层结构是数组,数组中每个元素是一个弱引用的指针,

用数组是因为,我们开发过程中可以一个线程可以有多个TreadLocal来存放不同类型的对象的,但是他们都将放到你当前线程的ThreadLocalMap里,所以肯定要数组来存。
private void set(ThreadLocal<?> key, Object value) {Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();if (k == key) {e.value = value;return;}if (k == null) {replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();}
从源码里面看到ThreadLocalMap在存储的时候会给每一个ThreadLocal对象一个threadLocalHashCode,在插入过程中,根据ThreadLocal对象的hash值,定位到table中的位置i,int i = key.threadLocalHashCode & (len-1)。
然后会判断一下:如果当前位置是空的,就初始化一个Entry对象放在位置i上;
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
如果位置i不为空,如果这个Entry对象的key正好是即将设置的key,那么就刷新Entry中的value;
if (k == key) {
e.value = value;
return;
}
如果位置i的不为空,而且key不等于entry,那就找下一个空位置,直到为空为止。
这样的话,在get的时候,也会根据ThreadLocal对象的hash值,定位到table中的位置,然后判断该位置Entry对象中的key是否和get的key一致,如果不一致,就判断下一个位置,set和get如果冲突严重的话,效率还是很低的。
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// get的时候一样是根据ThreadLocal获取到table的i值,然后查找数据拿到后会对比key是否相等 if (e != null && e.get() == key)。
while (e != null) {
ThreadLocal<?> k = e.get();
// 相等就直接返回,不相等就继续查找,找到相等位置。
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
ThreadLocal自身有一个计数器,用于生产每一个threadLocal对象的hash值,该值会和ThreadLocalMap中的数据长度进行hash运算,确定threadLocal的在数组中的存储位置
threadLocal.set()执行流程

ThreadLocal使用场景:
spring的@transactional注解,每个事务中,数据库connection都是放在自己的线程中的,不同的事务数据库connection是不一样的,同一个事务是使用一个数据库connection。具体代码是在org.springframework.transaction.support.TransactionSynchronizationManager中
自己在项目中使用的场景
- 重试机制
- 一个线程经常遇到横跨若干方法调用,需要传递的对象
为什么ThreadLocalMap的key要设计成弱引用
public void func1() {
ThreadLocal tl = new ThreadLocal<Integer>();
tl.set(100);
tl.get();
}

当func1方法执行完毕后,栈帧销毁,强引用 tl 也就没有了,但此时线程的ThreadLocalMap里某个entry的 k 引用还指向这个对象。若这个k 引用是强引用,就会导致k指向的ThreadLocal对象及v指向的对象不能被gc回收,造成内存泄漏;但是弱引用就不会有这个问题(弱引用的对象一旦遇到gc,如果该对象没有被强引用,则会被回收)。使用弱引用,entry的k会被gc回收,就可以使ThreadLocal对象在方法执行完毕后顺利被回收。
注意:虽然弱引用,保证了k指向的ThreadLocal对象能被及时回收,但是v指向的对象没有被回收,虽然ThreadLocalMap调用get、set时发现k为null时会去回收整个entry、value,但是有可能长时间没有调用get、set方法,因此弱引用不能保证内存完全不泄露。我们要在不使用某个ThreadLocal对象后,手动调用remoev方法来删除它
内存泄漏问题


ThreadLocal在保存的时候会把自己当做Key存在ThreadLocalMap中,正常情况应该是key和value都应该被外界强引用才对,但是现在key被设计成WeakReference弱引用了。
弱引用的对象一旦遇到gc,如果该对象没有被强引用,则会被回收
这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value指向的对象就有可能一直得不到回收,发生内存泄露。
就比如线程池里面的线程,线程都是复用的,那么之前的线程实例处理完之后,出于复用的目的线程依然存活,所以,ThreadLocal设定的value值被持有,导致内存泄露。
按照道理一个线程使用完,ThreadLocalMap是应该要被清空的,但是现在线程被复用了。
当然,如果线程不存在被复用的情况,就不会有这种内存泄漏的问题
解决方法
在代码的最后使用remove就好了,我们只要记得在使用的最后用remove把值清空就好了。
ThreadLocal<String> localName = new ThreadLocal();
try {
localName.set("张三");
……
} finally {
localName.remove();
}




