2015-10-27 137 views
6

我有一個小應用程序分配一些25000線程,然後釋放它們。 線程正在釋放時,即使在所有線程退出後,應用程序的內存消耗也會上升並保持較高水平。Java在釋放線程時泄漏堆內存

頂部看起來是這樣的:

PID USER  PR NI VIRT RES SHR S %CPU %MEM  TIME+ COMMAND 
9133 root  20 0 22.601g 8.612g 12080 S 0.0 9.1 1:18.61 java 

JMAP -heap看起來是這樣的:

Attaching to process ID 9133, please wait... 
Debugger attached successfully. 
Server compiler detected. 
JVM version is 25.66-b17 

using thread-local object allocation. 
Parallel GC with 18 thread(s) 

Heap Configuration: 
    MinHeapFreeRatio   = 40 
    MaxHeapFreeRatio   = 100 
    MaxHeapSize    = 1073741824 (1024.0MB) 
    NewSize     = 104857600 (100.0MB) 
    MaxNewSize    = 104857600 (100.0MB) 
    OldSize     = 968884224 (924.0MB) 
    NewRatio     = 2 
    SurvivorRatio   = 8 
    MetaspaceSize   = 21807104 (20.796875MB) 
    CompressedClassSpaceSize = 1073741824 (1024.0MB) 
    MaxMetaspaceSize   = 1073741824 (1024.0MB) 
    G1HeapRegionSize   = 0 (0.0MB) 

Heap Usage: 
PS Young Generation 
Eden Space: 
    capacity = 78643200 (75.0MB) 
    used  = 1572864 (1.5MB) 
    free  = 77070336 (73.5MB) 
    2.0% used 
From Space: 
    capacity = 13107200 (12.5MB) 
    used  = 0 (0.0MB) 
    free  = 13107200 (12.5MB) 
    0.0% used 
To Space: 
    capacity = 13107200 (12.5MB) 
    used  = 0 (0.0MB) 
    free  = 13107200 (12.5MB) 
    0.0% used 
PS Old Generation 
    capacity = 968884224 (924.0MB) 
    used  = 1264416 (1.205841064453125MB) 
    free  = 967619808 (922.7941589355469MB) 
    0.13050227970271916% used 

808 interned Strings occupying 54648 bytes. 

據我所看到的,沒有什麼在JMAP報告可以解釋頂部報告的8.612g。

Java版本的Oracle 1.8.0_66

應用紅帽企業Linux服務器上運行7.1版(米埔)。

應用程序的代碼如下:

import java.util.*; 
import java.util.concurrent.atomic.AtomicInteger; 

public class WaitTest { 
    static AtomicInteger ai = new AtomicInteger(0); 

    public static void main(String[] args) { 
     List<WaitingThread> threads = new LinkedList<>(); 
     while (true) { 
      System.out.println("number of threads: " + ai.get()); 
      String s = System.console().readLine(); 
      if (s == null) 
       System.exit(0); 
      s = s.trim(); 
      if (s.isEmpty()) 
       continue; 
      char command = s.charAt(0); 
      if (command != '+' && command != '-') { 
       System.out.println("+ or - please"); 
       continue; 
      } 
      String num = s.substring(1); 
      int iNum; 
      try { 
       iNum = Integer.parseInt(num.trim()); 
      } catch (Exception ex) { 
       System.out.println("valid number please"); 
       continue; 
      } 
      if (command == '+') { 
       for (int i = 0; i < iNum; i++) { 
        WaitingThread t = new WaitingThread(); 
        t.start(); 
        threads.add(t); 
       } 
      } 
      if (command == '-') { 
       Set<WaitingThread> threadsToJoin = new HashSet<>(); 
       for (Iterator<WaitingThread> it = threads.iterator(); it.hasNext();) { 
        if (iNum > 0) { 
         WaitingThread t = it.next(); 
         threadsToJoin.add(t); 

         synchronized (t.lock) { 
          t.lock.notify(); 
         } 

         it.remove(); 
         iNum--; 
        } else 
         break; 

       } 
       for (WaitingThread t : threadsToJoin) 
        try { 
         t.join(); 
        } catch (InterruptedException e) { 
         e.printStackTrace(); 
        } 
      } 
      System.gc(); 
     } 
    } 

    static class WaitingThread extends Thread { 
     public final Object lock = new Object(); 

     @Override 
     public void run() { 
      ai.incrementAndGet(); 
      try { 
       deepStack(200); 
      } catch (InterruptedException ex) { 
      } catch (Exception ex) { 
       System.out.println("exception in thread " + Thread.currentThread().getName() + ", " + ex.getMessage()); 
      } finally { 
       ai.decrementAndGet(); 
      } 
     } 

     private void deepStack(int depth) throws InterruptedException { 
      if (depth == 0) { 
       synchronized (lock) { 
        lock.wait(); 
       } 

      } else 
       deepStack(depth - 1); 
     } 
    } 

} 

Here is a link to pmap output:

Here is a link to jmap -dump (hprof)

+1

您必須爲您的進程申請堆轉儲 –

+1

發佈'pmap PID'的輸出。請參閱http://man7.org/linux/man-pages/man1/pmap.1.html –

+0

您正在線程中保留對線程的所有引用,即使線程完成後它們仍將保留在內存中。 – nafas

回答

0

列表中的變量'threads'不是所有的線程首先得到命令期間創建的 「+」 難道這永遠不會超出範圍?這意味着鏈表將持有對所有線程的引用,並且不會被垃圾收集,直到退出Main()。另外,它是possible that the LinkedList itself is leaking memory。即使這不是問題,你應該考慮using an ArrayList instead of a LinkedList

此外,這是另一個問題,詢問有關finding memory leaks in Java

+0

凱利,列表變量'threads'在「 - 」命令中被清除。你也可以在附加的hprof文件中看到「線程」列表是完全空的。 – Amir

+0

你可以切換到使用ArrayList並查看是否會影響內存使用情況? –

+0

當然。 切換到ArrayList。 對內存使用沒有顯着影響。 – Amir

0

應用程序內存只是鬆散地耦合到堆大小。當GC運行並清理事物時,清理堆,而不是系統內存。事實上,即使JVM經常釋放堆空間,它甚至可能不會進行系統調用來減少系統內存。

0

每個線程都需要一個堆棧(分配的內存),它不是java堆的一部分。

堆棧的默認大小取決於的JVM版本,操作系統等。

您可以用-Xss JVM參數調整它。

25000線程可以很容易地解釋你8.6克

更新以下阿米爾評論:

不是100%肯定,但在尋找旅遊PMAP日誌,你可能有競技場分配中存在的問題glibc

看到所有的64Mb分配

檢查你的glibc的版本是> 2 。10

嘗試使用環境變量MALLOC_ARENA_MAX = 2設置運行程序。你可以玩實際價值。

+0

謝謝,benbenw, 但問題是爲什麼即使所有的線程退出後,應用程序的內存消耗仍然很高。 – Amir

+0

更新了我的回答 – benbenw

+0

benbenw, 我通過執行/lib/libc.so.6檢查了我的glibc版本。 它是2.17。 我跑我的程序與MALLOC_ARENA_MAX = 1, 然後用MALLOC_ARENA_MAX = 2, 然後用MALLOC_ARENA_MAX = 10, 然後用MALLOC_ARENA_MAX = 1000。 同樣的結果。 – Amir