当前位置:网站首页>ThreadLocal giant pit! Memory leaks are just Pediatrics

ThreadLocal giant pit! Memory leaks are just Pediatrics

2022-06-26 14:06:00 Hollis Chuang

Hollis In the limited time discount for your new book , An in-depth explanation Java Basic dry goods notes !

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 .

08708f59b04667ce57c98af18c4c8f88.png

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 ~

7e692cffbb1fcb105f3c6aa29160fc98.png

Long press to scan code and enjoy 6 A discount

Previous recommendation

5937a3f3b90931e365cce0e5fcac1196.png

How does e-commerce red envelope rain come true ? Take it to the interview ( Typical high concurrency )


1581bdc11437efaa68d28f156e3dd7ed.png

ArrayList#subList These four pits , If you're not careful, you'll get caught


177fccb3c0ca770231948dea8c44247e.png

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

39774e1e9448174f3e687c6d1ed0eedf.png

Good article , I was watching ️

原网站

版权声明
本文为[Hollis Chuang]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/177/202206261313540495.html