当前位置:网站首页>ThreadLocal giant pit! Memory leaks are just Pediatrics
ThreadLocal giant pit! Memory leaks are just Pediatrics
2022-06-26 14:06:00 【Hollis Chuang】
I'm attending Code Review More than once I heard a classmate say : The context tool I wrote is OK , I have been running online for a long time . In fact, this idea is problematic ,ThreadLocal It is difficult to make mistakes , But it's easy to use it wrong .
This article will summarize in detail ThreadLocal Easy to use the wrong three pits :
Memory leak
Thread context is missing from the thread pool
Thread context is lost in parallel flow
Memory leak
because ThreadLocal Of key Is a weak reference , Therefore, if you do not call after use remove Cleaning up will lead to corresponding value Memory leak .
@Test
public void testThreadLocalMemoryLeaks() {
ThreadLocal<List<Integer>> localCache = new ThreadLocal<>();
List<Integer> cacheInstance = new ArrayList<>(10000);
localCache.set(cacheInstance);
localCache = new ThreadLocal<>();
}When localCache After the value of is reset cacheInstance By ThreadLocalMap Medium value quote , Can't be GC, But its key Yes ThreadLocal Instance reference is a weak reference .
Originally ThreadLocal Instances of are localCache and ThreadLocalMap Of key At the same time quote , But when localCache After the reference of is reset , be ThreadLocal Only ThreadLocalMap Of key Such a weak reference , At this point, the instance is in GC Can be cleaned up .

Actually, I have seen ThreadLocal Students of the source code will know ,ThreadLocal For itself key by null Of Entity There is a process of self-cleaning , But this process depends on the follow-up ThreadLocal Continue to use .
Suppose the above code is in a second kill scenario , There will be an instantaneous flow peak , This traffic peak will also drive the memory of the cluster to a high level ( Or if you are unlucky, you can directly fill the cluster memory and cause a failure ).
Since the peak flow has passed , Yes ThreadLocal The call also drops , Will make ThreadLocal The self-cleaning ability of decreases , Causing memory leaks .
ThreadLocal Self cleaning is the icing on the cake , Don't expect him to send carbon in the snow .
Compared with ThreadLocal Stored in the value Object leakage ,ThreadLocal Use in web It is more important to pay attention to the ClassLoader Let the cat out of the .
Tomcat On the official website web Use in container ThreadLocal A summary of memory leaks caused by , See :
https://cwiki.apache.org/confluence/display/tomcat/MemoryLeakProtectionHere we give an example , be familiar with Tomcat My classmates know ,Tomcat Medium web Application by Webapp Classloader Of this class loader .
also Webapp Classloader It destroys the implementation of the parental delegation mechanism , That all the web Application starts with Webapp classloader load , The advantage of this is that you can make web Application and dependency isolation .
Let's take a look at a specific example of a memory leak :
public class MyCounter {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class MyThreadLocal extends ThreadLocal<MyCounter> {
}
public class LeakingServlet extends HttpServlet {
private static MyThreadLocal myThreadLocal = new MyThreadLocal();
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
MyCounter counter = myThreadLocal.get();
if (counter == null) {
counter = new MyCounter();
myThreadLocal.set(counter);
}
response.getWriter().println(
"The current thread served this servlet " + counter.getCount()
+ " times");
counter.increment();
}
}There are two key points to note in this example :
MyCounter as well as MyThreadLocal It must be put in web Application path , Insured Webapp Classloader load .
ThreadLocal Class must be ThreadLocal The inheritance class of , For example, in the example MyThreadLocal, because ThreadLocal Was originally Common Classloader load , Its life cycle and Tomcat The container is the same .ThreadLocal The inheritance classes of include the more common NamedThreadLocal, Be careful not to step on the pit .
If LeakingServlet Where Web App launch ,MyThreadLocal Classes are also Webapp Classloader load .
If at this time web Apply offline , The thread's life cycle is not over ( For example LeakingServlet The service providing thread is a thread in a thread pool ).
That will lead to myThreadLocal The instance of is still referenced by this thread , Instead of being GC, At the beginning, it seems that the problem caused by this is not big , because myThreadLocal The referenced object does not occupy much memory space .
The problem lies in myThreadLocal Indirectly hold load web Applied webapp classloader References to ( adopt myThreadLocal.getClass().getClassLoader() You can refer to ).
And load web Applied webapp classloader There are references that hold all the classes it loads , This leads to Classloader Let the cat out of the , The memory leakage is very considerable .
Thread context is missing from the thread pool
ThreadLocal Cannot pass... In parent-child threads , So the most common way is to put the parent thread's ThreadLocal Value is copied to the child thread .
So you will often see code like the following :
for(value in valueList){
Future<?> taskResult = threadPool.submit(new BizTask(ContextHolder.get()));// Submit tasks , And set the copy Context To child thread
results.add(taskResult);
}
for(result in results){
result.get();// Block waiting for task execution to complete
}The submitted task definition looks like this :
class BizTask<T> implements Callable<T> {
private String session = null;
public BizTask(String session) {
this.session = session;
}
@Override
public T call(){
try {
ContextHolder.set(this.session);
// Execute business logic
} catch(Exception e){
//log error
} finally {
ContextHolder.remove(); // clear ThreadLocal The context of , Avoid thread reuse context Mutual string
}
return null;
}
}The corresponding thread context management class is :
class ContextHolder {
private static ThreadLocal<String> localThreadCache = new ThreadLocal<>();
public static void set(String cacheValue) {
localThreadCache.set(cacheValue);
}
public static String get() {
return localThreadCache.get();
}
public static void remove() {
localThreadCache.remove();
}
}There's no problem with that , Let's look at the thread pool settings :
ThreadPoolExecutor executorPool = new ThreadPoolExecutor(20, 40, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(40), new XXXThreadFactory(), ThreadPoolExecutor.CallerRunsPolicy);The last parameter controls when the thread pool is full , How to handle the submitted task , Built in 4 Strategies :
ThreadPoolExecutor.AbortPolicy // Throw an exception directly
ThreadPoolExecutor.DiscardPolicy // Discard the current task
ThreadPoolExecutor.DiscardOldestPolicy // Discard the task at the head of the work queue
ThreadPoolExecutor.CallerRunsPolicy // To serial execution You can see , When we initialize the thread pool, we specify that if the thread pool is full , Then the newly submitted task is converted to serial execution .
Then there will be a problem with our previous writing , Called when executing serially ContextHolder.remove(); The context of the main thread will also be cleaned up , Even if the thread pool continues to work in parallel later , The context passed to the child thread is already null 了 , And such problems are difficult to find in the pre - test .
Thread context is lost in parallel flow
If ThreadLocal Encountered parallel stream , Many interesting things will happen .
Let's say I have the following code :
class ParallelProcessor<T> {
public void process(List<T> dataList) {
// Check the parameters first , Space limit, omit first
dataList.parallelStream().forEach(entry -> {
doIt();
});
}
private void doIt() {
String session = ContextHolder.get();
// do something
}
}This code is easy to find that it doesn't work as expected during offline testing , Because the underlying implementation of parallel flow is also a ForkJoin Thread pool , Since it's a thread pool , that ContextHolder.get() What may be taken out is one null.
Let's change the code along this line :
class ParallelProcessor<T> {
private String session;
public ParallelProcessor(String session) {
this.session = session;
}
public void process(List<T> dataList) {
// Check the parameters first , Space limit, omit first
dataList.parallelStream().forEach(entry -> {
try {
ContextHolder.set(session);
// Business processing
doIt();
} catch (Exception e) {
// log it
} finally {
ContextHolder.remove();
}
});
}
private void doIt() {
String session = ContextHolder.get();
// do something
}
}Can the modified code work ? If you are lucky , You will find that there are problems with this change , Bad luck , This code works well offline , This code goes online smoothly . Soon you will find that there are some other very strange things in the system bug.
The reason is that the design of parallel flow is special , The parent thread may also participate in the scheduling of the parallel streamline process pool , If the above process Method is executed by the parent thread , Then the context of the parent thread will be cleaned up . The context that causes subsequent copies to the child thread is null, It also causes the problem of losing context .
End
My new book 《 In depth understanding of Java The core technology 》 It's on the market , After listing, it has been ranked in Jingdong best seller list for several times , At present 6 In the discount , If you want to start, don't miss it ~ Long press the QR code to buy ~

Long press to scan code and enjoy 6 A discount
Previous recommendation
ArrayList#subList These four pits , If you're not careful, you'll get caught
Go to OPPO interview , I got numb when I was asked ...
There is Tao without skill , It can be done with skill ; No way with skill , Stop at surgery
Welcome to pay attention Java Road official account

Good article , I was watching ️
边栏推荐
- Luogu p4513 xiaobaiguang Park
- 【MySQL从入门到精通】【高级篇】(二)MySQL目录结构与表在文件系统中的表示
- 虫子 运算符重载的一个好玩的
- I met the problem of concurrent programming in an interview: how to safely interrupt a running thread
- GC is not used in D
- D中不用GC
- Bug STL string
- Taishan Office Technology Lecture: four cases of using bold font
- Wechat applet magic bug - choose to replace the token instead of clearing the token, wx Getstoragesync will take the old token value instead of the new token value
- Solutions to the failure of last child and first child styles of wechat applet
猜你喜欢

Es snapshot based data backup and restore

Gurivat sprint Harbour Exchange listed: created “multiple first”, received 900 million yuan Investment from IDG capital

2021-10-18 character array

9項規定6個嚴禁!教育部、應急管理部聯合印發《校外培訓機構消防安全管理九項規定》

Wechat applet -picker component is repackaged and the disabled attribute is added -- below

Common operation and Principle Exploration of stream

微信小程序注册指引

What is the use of index aliases in ES

ICML 2022 | limo: a new method for rapid generation of targeted molecules

ES中索引别名(alias)的到底有什么用
随机推荐
Design of PHP asymmetric encryption algorithm (RSA) encryption mechanism
Included angle of 3D vector
网络远程访问的方式使用树莓派
Generation and rendering of VTK cylinder
程序员必备,一款让你提高工作效率N倍的神器uTools
7-16 monetary system I
Pytorch based generation countermeasure Network Practice (7) -- using pytorch to build SGAN (semi supervised GaN) to generate handwritten digits and classify them
虫子 类和对象 中
Es common grammar I
GO语言-管道channel
Input text to automatically generate images. It's fun!
Original code, inverse code and complement code
Use performance to see what the browser is doing
使用 Performance 看看浏览器在做什么
PHP非对称加密算法(RSA)加密机制设计
Luogu p4145 seven minutes of God created questions 2 / Huashen travels around the world
7-1 range of numbers
9項規定6個嚴禁!教育部、應急管理部聯合印發《校外培訓機構消防安全管理九項規定》
Bucket of P (segment tree + linear basis)
The most critical elements of team management


