Java在释放线程时从堆中泄漏内存

Java在释放线程时从堆中泄漏内存,java,memory-leaks,Java,Memory Leaks,我有一个小应用程序,它分配大约25000个线程,然后释放它们。 当线程被释放时,应用程序的内存消耗会增加,即使在所有线程退出后,内存消耗仍然很高 顶部如下所示: 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 Attaching to process

我有一个小应用程序,它分配大约25000个线程,然后释放它们。 当线程被释放时,应用程序的内存消耗会增加,即使在所有线程退出后,内存消耗仍然很高

顶部如下所示:

 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
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-heap看起来是这样的:

 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
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报告中没有任何内容可以解释top报告的8.612g

Java版本是oracle 1.8.0_66

该应用程序运行在Red Hat Enterprise Linux Server 7.1版(Maipo)上

申请的代码如下:

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);
        }
    }

}
import java.util.*;
导入java.util.concurrent.AtomicInteger;
公共类等待测试{
静态AtomicInteger ai=新的AtomicInteger(0);
公共静态void main(字符串[]args){
列表线程=新建LinkedList();
while(true){
System.out.println(“线程数:+ai.get());
字符串s=System.console().readLine();
如果(s==null)
系统出口(0);
s=s.修剪();
如果(s.isEmpty())
继续;
char命令=s.charAt(0);
if(command!='+'&&command!='-'){
System.out.println(“+或-请”);
继续;
}
String num=s.substring(1);
国际单位制;
试一试{
iNum=Integer.parseInt(num.trim());
}捕获(例外情况除外){
System.out.println(“请输入有效号码”);
继续;
}
如果(命令=='+'){
对于(int i=0;i0){
WaitingThread t=it.next();
螺纹连接。添加(t);
同步(t.lock){
t、 lock.notify();
}
it.remove();
因纽姆--;
}否则
打破
}
for(WaitingThread t:threadsToJoin)
试一试{
t、 join();
}捕捉(中断异常e){
e、 printStackTrace();
}
}
gc();
}
}
静态类WaitingThread扩展线程{
公共最终对象锁=新对象();
@凌驾
公开募捐{
ai.incrementAndGet();
试一试{
深烟囱(200);
}捕获(中断异常例外){
}捕获(例外情况除外){
System.out.println(“线程中的异常”+thread.currentThread().getName()+”,“+ex.getMessage());
}最后{
ai.decrementAndGet();
}
}
私有void deepStack(int depth)抛出InterruptedException{
如果(深度==0){
已同步(锁定){
lock.wait();
}
}否则
深度叠加(深度-1);
}
}
}


不是所有线程都是在列表变量
'threads'
中的“+”命令期间首先创建的,该命令永远不会超出范围吗?这意味着链表包含对所有线程的引用,并且在退出
Main()
之前不会被垃圾收集。而且是。即使这不是你应该考虑的问题。
另外,这里还有另一个问题。

应用程序内存只与堆大小松散耦合。当GC运行并清理东西时,它会清理堆,而不是系统内存。事实上,JVM甚至可能不会进行系统调用以减少系统内存,即使它经常释放堆空间。

每个线程都需要一个堆栈(分配的内存),它不是java堆的一部分

堆栈的默认大小取决于jvm版本、操作系统等

可以使用-Xss jvm参数对其进行调优

25000个线程可以很容易地解释您的8.6g

更新以下阿米尔评论:

不是100%确定,但查看巡演pmap日志,您可能在中的竞技场分配器有问题

请参阅所有64Mb分配

检查您的glibc版本是否>2.10


尝试使用环境变量MALLOC_ARENA_MAX=2 set运行程序。您可以使用实际值。

您必须在
pmap-PID
输出后请求进程的堆转储。请看,您正在将对线程的所有引用保留在threadsToJoin中,即使线程完成后,它们仍将在内存中…nafas,threadsToJoin是本地的,并且在块开始时从内存中删除,如果(命令=='-')退出。JVM不会将内存释放回操作系统。Kelly,列表变量“threads”在“-”命令期间被清除。您还可以在附加的hprof文件中看到“线程”列表完全为空。您能否切换到使用ArrayList,看看这是否会影响内存使用?当然。切换到ArrayList。对内存使用没有显著影响。谢谢,benbenw,但问题是为什么即使在所有线程退出后,应用程序的内存消耗仍然很高。benbenw,我通过执行/lib/libc.so.6检查了我的glibc版本。是2点17分。我用MALLOC_ARENA_MAX=1、MALLOC_ARENA_MAX=2、MALLOC_ARENA_MAX=10、MALLOC_ARENA_MAX=1000运行程序。同样的结果,你是怎么设定的?使用-D MALLOC_ARENA_MAX=xx或操作系统环境