
问题一:为什么 ThreadLocalMap 的 key 是弱引用?
【假设 Entry 的 key 是对 ThreadLocal 对象的强引用】:这个 Entry
又持有 ThreadLocal 对象和 value 对象的强引用。如果在其他地方都没有对这个 ThreadLocla 对象的引用了、然后在使用 ThreadLocalMap 的过程中又没有正确地在用完后就调用 remove 方法、所以这个 ThreadLocal 对象和所关联的 value 对象就会跟随着线程一直存在、这样就会可能会造成内存泄漏问题。
特别是在使用线程池的时候、核心线程是会一直存在直到程序结束、如果这些线程中的 ThreadLocalMap 中的数据没有被及时清理、就会一直占用内存、而且在线程复用时可能会导致数据错乱的危险。
【Entry 的 key 是对 ThreadLocal 对象的弱引用】:弱引用就意味着、如果没有其他引用对象的强引用关系、那么这个仅被弱引用引用着的对象在下次 GC 时就会被回收掉、这样在一定程度上降低内存泄漏的风险。但同时也引入了新的问题、key 虽然被回收了、但是 value 对象还在、我们无法获取、也无法删除、这样也会存在内存泄漏的风险。
虽然 ThreadLocalMap 中在进行 set 和 get 操作时会进行启发式清理和探测式清理、清理一部分 key 为 null 的 Entry 对象、但是这也只是一种后备选择方案
最重要的还是开发人员在编写代码时记得在使用完数据后及时调用 remove() 方法手动清理
补充:
【内存泄漏就是:有些对象已经不再使用了、但是由于没有正确处理对象的引用关系、使得这个无用的对象还一直被 GC Root 直接或间接引用着、垃圾回收时就无法清理掉这些对象、如果这类对象存在很多、就会导致内存泄漏。简单地说就是有些无用对象占用着宝贵的内存空间、但又没办法清理掉它们 可达性分析是现代垃圾回收器用来判断对象是否存活的核心算法】
问题二:为什么 ThreadLocalMap 的 value 是强引用?
【假设Entry 的 value 是弱引用】:假设 key 所引用的 ThreadLocal 对象还被其他的引用对象强引用着,那么这个 ThreadLocal 对象就不会被 GC 回收、但如果 value 是弱引用且不被其他引用对象引用着、那 GC 的时候就被回收掉了、那线程通过 ThreadLocal 来获取 value 的时候就会获得 null,显然这不是我们希望的结果。因为对我们来说、value 才是我们想要保存的数据、ThreadLcoal 只是用来关联 value 的、如果 value 都没了、还要 ThreadLocal 干啥呢
面试参考回答:
面试官您好关于 ThreadLocalMap 的 key 使用 弱引用 、value 使用强引用的问题
我的理解是这样的:
首先要理解这样设计的目的是为了尽可能地避免内存泄漏。
-
Key 使用 弱引用 : 假设 key 是强引用、那么即使 ThreadLocal 对象本身已经没有其他地方引用了、由于 ThreadLocalMap 中 Entry 的强引用、这个 ThreadLocal 对象仍然无法被垃圾回收。如果线程一直存活(比如线程池中的线程)、这个 ThreadLocal 对象和对应的 value 就会一直占用内存、造成内存泄漏。使用弱引用、当 ThreadLocal 对象没有外部强引用时、在下次 GC 的时候、key 就会被回收、降低了内存泄漏的风险。
-
Value 使用强引用: Value 是我们真正想要存储的数据,如果 value 也使用弱引用、那么在 ThreadLocal 对象还存活的情况下、
value
却可能因为没有强引用而被 GC 回收、导致我们通过 ThreadLocal 获取到的 value 为空、这显然是不符合ThreadLocal
的设计目的的。ThreadLocal 的作用就是关联数据、如果数据都没了ThreadLocal 就失去了意义。
而且虽然 key 使用 弱引用 可以降低内存泄漏的风险、但仍然存在 value 无法回收的问题。
当 key 被回收后value 仍然被 Entry 强引用。如果线程一直存活、这个 value 就会一直占用内存。
因此ThreadLocalMap 在 set()
和 get()
操作时会进行 启发式 清理、移除 key 为 null 的 Entry 但这只是一个补救措施。
最根本的解决办法还是需要开发者在使用完 ThreadLocal 后、手动调用 remove()
方法、及时清理 ThreadLocalMap 中的 Entry,避免内存泄漏。
总结: 弱引用 key 降低了 ThreadLocal 对象本身的内存泄漏风险、强引用 value 保证了数据的可用性。
但最终避免内存泄漏、需要开发者养成良好的习惯、及时清理 ThreadLocal。