当前位置:网站首页>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/MemoryLeakProtection
Here 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 ️
边栏推荐
- How to check if a text field is empty or not in swift
- Generation and rendering of VTK cylinder
- Original code, inverse code and complement code
- 7.consul service registration and discovery
- Luogu p4145 seven minutes of God created questions 2 / Huashen travels around the world
- 虫子 运算符重载的一个好玩的
- Reprint - easy to use wechat applet UI component library
- character constants
- DataGrip配置的连接迁移
- Global variable vs local variable
猜你喜欢
随机推荐
Global variable vs local variable
MongoDB系列之Window环境部署配置
Range of types
[wc2006] director of water management
Educational Codeforces Round 117 (Rated for Div. 2)E. Messages
A must for programmers, an artifact utools that can improve your work efficiency n times
D check type is pointer
Detailed practical sharing, two hours of funny videos after work, earning more than 7000 a month
Zero basics of C language lesson 8: Functions
Es snapshot based data backup and restore
虫子 内存管理 上
[MySQL from introduction to mastery] [advanced part] (II) representation of MySQL directory structure and tables in the file system
In insect classes and objects
虫子 类和对象 中
Wechat applet -picker component is repackaged and the disabled attribute is added -- below
Niuke challenge 53:c. strange magic array
【Proteus仿真】Arduino UNO按键启停 + PWM 调速控制直流电机转速
免费的机器学习数据集网站(6300+数据集)
"Scoi2016" delicious problem solution
Applicable and inapplicable scenarios of mongodb series