java使用局部線程池爲何會形成線程泄露

java使用局部線程池爲何會形成線程泄露

 

1、思考 - 形成泄露,確定是沒法被GC回收,那爲何局部線程池沒有被回收,咱們來經過源碼一探究竟

   這裏先給出結論:ThreadPoolExecutor  ->   Worker   ->  Thread    因爲存在這樣的引用關係,而且 Thread 做爲 GC Root ,因此沒法被回收

2、經過ThreadPoolExecutor類對源碼一探究竟  不詳解

ExecutorService threadPool = new ThreadPoolExecutor(
                1,
                1,
                300,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1),
                Executors.defaultThreadFactory()
        );

        threadPool.execute(() -> {
            System.out.println("sdf");
        });

   1.進入threadPool.execute()方法,以下圖

                      

                圖1

  2.重點是addWorker方法,不廢話,直接進入圖1紅色框addWorker(command, true) 這個方法

    

                    圖2

  3.關鍵的3步已經標出來了,看上圖

      3.1 先說說第一步w = new Worker(firstTask); 直接進去,從圖3能夠看到Worker內存有兩個變量分別保存 一個待執行的Runnable和一個Thread

          重點看圖3紅色框裏的  this ,這個this 表明的Worker類的實例,也就是說線程start後執行的Runnable並非調用者傳進來的Runnable,而是Worker實例,那麼能夠猜想Worker是  implements Runnable 了,看圖4

          

                 圖3

          

                 圖4

      3.2 第二步是提取出 Worker 實例的 Thread,也就是第一步getThreadFactory().newThread(this) new 出來的線程

      3.3 第三步就是直接執行 Worker 實例的 Thread,到這裏咱們很清晰的知道了,線程池裏的線程最後執行的是Worker 而不是 調用者傳進來的Runnable

   4. 重點來了,追一下Worker的 run方法,一切將大白於天下

      

          圖5  

     4.1 分析圖6可知,最終執行仍是 (調用者)傳進來的Runnable,可是有一個問題Thread 執行完Runnable後並不會stop,而是會進入阻塞,見 紅色框1  進入 getTask()方法一探

      

                     圖6

     4.2 從圖7能夠看出

      4.2.1.如有數據 -> 則會返回Runnable 任務進入圖6中的while循環中執行

      4.2.2.若沒有數據  ->  則會阻塞(看圖7紅色框), 願意的能夠去看看juc中的阻塞隊列的實現,就能知道阻塞的原理了,這再也不這次範圍內

      

                   圖7

  5.總結

    到這裏咱們能夠獲得兩個信息:

      第一:爲何線程池中的線程能夠複用  ---  是由於線程池中的線程執行的是Worker的Run方法,而這裏面是一個至關於 while(true)的死循環,所以線程永遠不會有執行完的那一天

      第二:爲何不會被回收  ---    是由於存在GC ROOT 的引用,因此沒法被回收 。  引用以下

          ThreadPoolExecutor  ->   Worker   ->  Thread   

            因爲Thread 是 活着的,所以可做爲GC ROOT ,因此纔會看到 局部線程池ThreadPoolExecutor沒有被釋放,可做爲GC ROOT 的有如下,僅做參考

A garbage collection root is an object that is accessible from outside the heap. The following reasons make an object a GC root:

System Class
Class loaded by bootstrap/system class loader. For example, everything from the rt.jar like java.util.* .
JNI Local
Local variable in native code, such as user defined JNI code or JVM internal code.
JNI Global
Global variable in native code, such as user defined JNI code or JVM internal code.
Thread Block
Object referred to from a currently active thread block.
Thread
A started, but not stopped, thread.
Busy Monitor
Everything that has called wait() or notify() or that is synchronized. For example, by calling synchronized(Object) or by entering a synchronized method. Static method means class, non-static method means object.
Java Local
Local variable. For example, input parameters or locally created objects of methods that are still in the stack of a thread.
Native Stack
In or out parameters in native code, such as user defined JNI code or JVM internal code. This is often the case as many methods have native parts and the objects handled as method parameters become GC roots. For example, parameters used for file/network I/O methods or reflection.
Finalizable
An object which is in a queue awaiting its finalizer to be run.
Unfinalized
An object which has a finalize method, but has not been finalized and is not yet on the finalizer queue.
Unreachable
An object which is unreachable from any other root, but has been marked as a root by MAT to retain objects which otherwise would not be included in the analysis.
Java Stack Frame
A Java stack frame, holding local variables. Only generated when the dump is parsed with the preference set to treat Java stack frames as objects.
Unknown
An object of unknown root type. Some dumps, such as IBM Portable Heap Dump files, do not have root information. For these dumps the MAT parser marks objects which are have no inbound references or are unreachable from any other root as roots of this type. This ensures that MAT retains all the objects in the dump.

 

      

  6.經過程序進行復現

    6.1 執行的代碼以下

public class JVMDemoTest {
    public static void main(String[] args) throws Exception {
        JVMDemoTest t = new JVMDemoTest();
        while (true) {
            Thread.sleep(1000);
            t.test();
        }
    }

    private void test() {
        for (int i = 0; i < 10; i++) {
            Executor mExecutors = Executors.newFixedThreadPool(3);
            for (int j = 0; j < 3; j++) {
                mExecutors.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("execute");
                    }
                });
            }
        }
    }
}

 

    6.2  使用jdk 自帶的工具   jvisualvm 監控線程以下(堆的最低點是屢次執行GC致使的),可見線程一直向上飆升

      

      6.3  使用jmap -dump:format=b,file=aaaa.hprof 46008  命令 dump 堆下來分析。我使用的分析工具是MAT 

        使用直方圖打開,能夠看到 ThreadPoolExecutor對象 有 640個,Worker對象有1923個,充分的說明 線程池並無被GC 回收

        咱們使用MAT提供的查看GC Root 工具查看如圖11. 從圖中可知  Thread 確實是GC Root 對象

        

            

         

                  圖11

   

  

鄙人小白,如有分析不到之處,望指正

相關文章
相關標籤/搜索