Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TransmittableThreadLocal会不会有内存泄漏的风险? #281

Closed
yexuerui opened this issue Jun 26, 2021 · 4 comments
Closed

TransmittableThreadLocal会不会有内存泄漏的风险? #281

yexuerui opened this issue Jun 26, 2021 · 4 comments
Assignees
Labels

Comments

@yexuerui
Copy link

yexuerui commented Jun 26, 2021

  • 声明的TransmittableThreadLocal一般为静态变量;
  • 或者在Springbean中声明(单例模式)。

TransmittableThreadLocal一直被强引用持有。

那么 holder即使设计的为InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>
若不在子线程显式调用remove()方法,也不会被GC回收吧。

@oldratlee
Copy link
Member

oldratlee commented Jun 26, 2021

……
那么 holder即使设计的为InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>
若不在子线程显式调用remove()方法,也不会被GC回收吧。

简单地说:不显式调用remove()方法,不被GC回收,是正确的功能。

如果 不调用remove()/重新set()ThreadLocal持有的对象被GC回收, 则是Bug@yexuerui
(无故消失,不能正确持有!)

……
TransmittableThreadLocal一直被强引用持有

TransmittableThreadLocal是否强引用一直持有(即如作为static字段)由业务需求决定。

对于ThreadLocal(含InheritableThreadLocal TransmittableThreadLocal),
一般场景、绝大多数场景下,static的方式是合理的;
具体需要 使用方 根据业务场景来分析来决定使用方式。

PS

与 下面的Issue 类似/重复:


关于内存泄漏

任何持有内存的字段或功能,都会有内存泄漏的风险;需要注意正确使用。

TransmittableThreadLocal功能本身没有内存泄漏的问题。 @yexuerui
TransmittableThreadLocal在正确使用的前提下,业务逻辑不会内存泄漏。

相关Issue

@oldratlee oldratlee self-assigned this Jun 26, 2021
@oldratlee oldratlee added the ❓question Further information is requested label Jun 26, 2021
@oldratlee
Copy link
Member

oldratlee commented Jun 26, 2021

@yexuerui 有涉及出现内存泄漏、具体业务场景下碰到的实际问题吗?

  • 上面我泛泛讨论内存泄漏,可能不方便有体感。
  • 对于你分析与理解内存泄漏也会比较困难。 😁

@oldratlee oldratlee changed the title transmittable-thread-local会不会有内存泄漏的风险? TransmittableThreadLocal会不会有内存泄漏的风险? Jun 26, 2021
@yexuerui
Copy link
Author

yexuerui commented Jun 26, 2021

因为TransmittableThreadLocal继承的是InheritableThreadLocal

  • 在某个线程池第一次创建线程的时候,执行Threadinit方法,完成的数据的跨线程传递。
    (即子线程的InheritableThreadLocalinheritableThreadLocals会存储值)
  • holder也是InheritableThreadLocal变量,在子线程创建时,执行init方法。子线程的holder也会继承父线程的值。

前提:

  1. 线程池的线程第一次被创建;
  2. 在创建子线程前,主线程中调用了TransmittableThreadLocalset方法;
  3. 主线程结束后,调用remove方法;
  4. 请求再次进来,调用同一个线程池,发现子线程的InheritableThreadLocal依旧存在步骤2存入的值。

代码验证

image

local/t4的dubug效果:
image

也就是在第一次创建Thread时,会有些对象无法回收。

那么上述,如何清除泄漏的对象呢?


我理解TransmittableThreadLocal

restoreTtlValues方法中,会清除子线程设置的holderTransmittableThreadLocal值(用户无需调用remove方法),
但是我想问的是,在线程第一次创建的holder如何被清除?


我个人理解,普通线程池(非ForkJoinPool),且不是CallerRunsPolicy拒绝策略。
子线程调用remove()方法,父线程的TransmittableThreadLocal不会被remove吧?

@oldratlee
Copy link
Member

oldratlee commented Jun 26, 2021

1. 关于 Inheritable能力/功能 (即创建Thread时的上下文传递)引发的问题

……
4. 请求再次进来,调用同一个线程池,发现子线程的InheritableThreadLocal依旧存在步骤2存入的值。

……

也就是在第一次创建Thread时,会有些对象无法回收。
那么上述,如何清除泄漏的对象呢?

……

但是我想问的是,在线程第一次创建的holder如何被清除?

上面你讨论的是:
Inheritable能力/功能 (即创建Thread时的上下文传递)引发的问题。

解决方法 参见 #279 (comment)@yexuerui

TTL提供了 关闭 Inheritable能力的解法:
(可能要理解一下 这个解法及其背景原因,有些复杂性。 😄 )

2. 关于Remove的时机

我个人理解,普通线程池(非ForkJoinPool),且不是CallerRunsPolicy拒绝策略。
子线程调用remove()方法,父线程的TransmittableThreadLocal不会被remove吧?

子线程remove/set操作不应该影响父线程。更进一步表述:

  • ThreadLocal值在不同线程之间是独立的(这其实是ThreadLocal的定义和命名)。
    • 即 更新操作 不会影响 别的线程的ThreadLocal值,这是正确的功能。
  • 在约定的时机会 传递

线程池 且拒绝策略 CallerRunsPolicy 也会 做remove@yexuerui
remove说成restore,是更合理、严谨的设计)。

『也会 做remove』原因 简单说明:
CRR操作的上下文的恢复(restore)操作是在try-finallyfinally中完成的。

更多的说明参见下面的内容:

文档 怎么设计一个『上下文传递流程』方案保证上下文的生命周期的正确性

本质原因是:ThreadLocalset/remove的上下文传递模式 在使用线程池等异步执行组件的情况下不再是有效的。常见的典型例子:

……

怎么设计一个『上下文传递流程』方案(即上下文的生命周期),以保证没有上面的问题?

期望:上下文生命周期的操作从业务逻辑中分离出来。业务逻辑不涉及生命周期,就不会有业务代码如疏忽清理而引发的问题了。整个上下文的传递流程或说生命周期可以规范化成:捕捉、回放和恢复这3个操作,即CRR(capture/replay/restore)模式

……

相关文档与Issue:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants