使用ExecutorService线程泄漏?

时间:2017-10-30 15:24:19

标签: java performance

我有一个繁忙的旅行应用程序,使用ExecutorService连接到像这样运行的后端服务

ExecutorService taskExecutor = Executors.newFixedThreadPool(10);
List<Future<MyResponse>> futures = new ArrayList<Future<MyResponse>>();

futures.add(taskExecutor.submit(myTask1));
futures.add(taskExecutor.submit(myTask2));
futures.add(taskExecutor.submit(myTask3));
for (int i=0; i<futures.size(); i++) {
    future = futures.get(i);
    // do something with "future"
}

taskExecutor.shutdownNow();

我发现它每小时都会崩溃,线程数达到峰值,如下图所示。上面的代码有问题吗?

enter image description here

这是崩溃时的摘要日志,它会留下一个名为hs_err_pid92053.log的文件

#
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map 12288 bytes for committing reserved memory.
# Possible reasons:
#   The system is out of physical RAM or swap space
#   In 32 bit mode, the process size limit was hit
# Possible solutions:
#   Reduce memory load on the system
#   Increase physical memory or swap space
#   Check if swap backing store is full
#   Use 64 bit Java on a 64 bit OS
#   Decrease Java heap size (-Xmx/-Xms)
#   Decrease number of Java threads
#   Decrease Java thread stack sizes (-Xss)
#   Set larger code cache with -XX:ReservedCodeCacheSize=
# This output file may be truncated or incomplete.
#
#  Out of Memory Error (os_linux.cpp:2627), pid=92053, tid=0x00007f273e0ef700
#
# JRE version: Java(TM) SE Runtime Environment (8.0_121-b13) (build 1.8.0_121-b13)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.121-b13 mixed mode linux-amd64 )
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#


---------------  S Y S T E M  ---------------

OS:CentOS Linux release 7.4.1708 (Core) 

uname:Linux 3.10.0-693.5.2.el7.x86_64 #1 SMP Fri Oct 20 20:32:50 UTC 2017 x86_64
libc:glibc 2.17 NPTL 2.17 
rlimit: STACK 8192k, CORE 0k, NPROC 160181, NOFILE 131072, AS infinity
load average:2.02 2.21 2.43

/proc/meminfo:
MemTotal:       41038016 kB
MemFree:         3875544 kB
MemAvailable:   26661144 kB
Buffers:           57724 kB
Cached:         22408624 kB
SwapCached:         1468 kB
Active:         23965100 kB
Inactive:       10993212 kB
Active(anon):   10868052 kB
Inactive(anon):  1649944 kB
Active(file):   13097048 kB
Inactive(file):  9343268 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:       8257532 kB
SwapFree:        8253832 kB
Dirty:              9564 kB
Writeback:             0 kB
AnonPages:      12490804 kB
Mapped:           171152 kB
Shmem:             25844 kB
Slab:            1053464 kB
SReclaimable:     780424 kB
SUnreclaim:       273040 kB
KernelStack:      529280 kB
PageTables:       101952 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:    28776540 kB
Committed_AS:   42016176 kB
VmallocTotal:   34359738367 kB
VmallocUsed:      259648 kB
VmallocChunk:   34359341052 kB
HardwareCorrupted:     0 kB
AnonHugePages:   6057984 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
DirectMap4k:      247744 kB
DirectMap2M:    41695232 kB

3 个答案:

答案 0 :(得分:0)

您使用Executors.newFixedThreadPool(10)获取ExecutorService来运行某些任务。获得的特定服务将拥有10个用于任务服务的线程,也许还有一个用于管理。您的代码还等待它提交的所有任务完成,然后关闭ExecutorService。因此,如果此代码确实对您提供的线程数负责,那么主要有两种可能性:

  1. 您的JVM或Java标准库中存在错误。
  2. 您呈现的代码本身会多次并行调用。
  3. 我对后者的评价更有可能。在这种情况下,如果繁忙应用程序上的负载超过主机的容量,那么这可能确实表现为活动线程的累积,因为创建的新线程的速率超过了完成和清理的线程的速率。 / p>

    您的特定代码对此没有帮助,因为,至少在示例中,您创建的ExecutorService线程数多于实际需要的数量。这将放大您观察到的效果,但额外的开销不足以导致它。您显然也会迅速创建并销毁ExecutorService。这也涉及不必要的开销。

    我不建议每次都设置和删除ExecutorService,而是建议在程序启动时设置合适的ExecutorService,并在程序的持续时间内重复使用相同的服务。作为直接的好处,这提供了对使用的线程总数进行更严格控制的可能性(如果继续使用固定大小的池)。它还应该减少你的线程管理开销。

    如果提交的任务不执行I / O,那么在您的(一个)池中拥有比拥有CPU内核更多的线程可能没有用,但如果它们确实执行了I / O,那么您可能会获得更高的利用率多一点。但不是数千人。

    还要考虑您是否只需要扩展主机。例如,通过负载平衡群集提供请求,这样任何单个主机都不会比清除请求更快地接收请求。

答案 1 :(得分:0)

最后,我想出了发生了什么。它是导致问题的JVM最大线程数的限制。

我在Linux中设置了以下内容

ulimit -u unlimited
echo 999999 > /proc/sys/kernel/threads-max
echo 999999 > /proc/sys/kernel/pid_max
echo 1999999 > /proc/sys/vm/max_map_count

然后使用

减小堆栈的大小
-Xss512k

答案 2 :(得分:-1)

我经常使用FixedThreadPool并且没有遇到泄漏(尽管考虑到各种JVM版本,操作系统和一般编码方案,总会有惊喜)。我想到的第一个注意事项仍然是:

  1. 最重要的是:你的代码是否总是达到'shutdownNow',或者它可能会被困在“做一些未来的事情”(我假设涉及等待)?例如。它是否会被卡在某些I / O或条件等待上,无限期超时?

  2. 您是否检查了其他可能的线程来源(其他 人民代码,第三方等。)