【问题标题】:Java leaks memory out of heap when releasing threadsJava在释放线程时会从堆中泄漏内存
【发布时间】:2016-01-26 22:27:31
【问题描述】:

我有一个小型应用程序,它分配了大约 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

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报告中没有什么可以解释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);
        }
    }

}

Here is a link to pmap output:

Here is a link to jmap -dump (hprof)

【问题讨论】:

  • 您必须获取进程的堆转储
  • 发布来自pmap PID 的输出。见man7.org/linux/man-pages/man1/pmap.1.html
  • 您将所有对线程的引用保存在threadToJoin中,即使在线程完成后它们仍会在内存中......
  • nafas,threadsT​​oJoin 是本地的,在以 if (command == '-') exits 开头的块后从内存中删除。
  • JVM 不会将内存释放回操作系统。

标签: java memory-leaks


【解决方案1】:

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

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

您可以使用 -Xss jvm 参数对其进行调整。

25000个线程可以轻松解释你的8.6g

更新以下 Amir 评论:

不是 100% 肯定,但查看游览 pmap 日志,您可能在 glibc 中的竞技场分配器有问题

查看所有 64Mb 分配

检查您的 glibc 版本是否 > 2.10

尝试在设置环境变量 MALLOC_ARENA_MAX=2 的情况下运行您的程序。您可以使用实际值。

【讨论】:

  • 谢谢,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 或使用 os env export MALLOC_ARENA_MAX=xx ?
  • 导出 MALLOC_ARENA_MAX=xx
  • 嗨,阿米尔!我在 RHEL7.4 上使用 glibc 版本 - 2.17 的巨大 java 内存也有同样的问题。我尝试了不同的 MALLOC_ARENA_MAX 值(4 和 2),但结果是一样的。你找到解决这个问题的方法了吗??跨度>
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    不是所有线程都首先在列表变量'threads' 中的命令“+”期间创建,它永远不会超出范围吗?这意味着链表持有对所有线程的引用,并且在Main() 退出之前不会被垃圾收集。另外,它是possible that the LinkedList itself is leaking memory。即使这不是问题,您也应该考虑using an ArrayList instead of a LinkedList

    另外,还有一个关于finding memory leaks in Java的问题。

    【讨论】:

    • Kelly,列表变量“线程”在“-”命令期间被清除。您还可以在附加的 hprof 文件中看到“线程”列表完全为空。
    • 你能不能改用 ArrayList 看看会不会影响内存使用?
    • 当然。切换到 ArrayList。对内存使用没有显着影响。
    猜你喜欢
    • 2021-02-25
    • 2012-10-11
    • 1970-01-01
    • 2012-06-08
    • 2016-10-02
    • 2016-12-09
    • 2012-02-17
    • 1970-01-01
    • 2014-09-19
    相关资源
    最近更新 更多