JVM 中不断增长的常驻大小集

Kis*_*esh 8 java linux memory jvm

我有一个在 64 位 LINUX 上运行的 JAVA 进程,版本为“CentOS Linux 版本 7.3.1611”,RAM 为 7.6GB。

下面是一些使用过的 JVM 标志,

  1. -Xmx3500m
  2. -Xms3500m
  3. -XX:MaxMetaspaceSize=400m
  4. -XX:CompressedClassSpaceSize=35m

注意:线程栈(1MB)和代码缓存(240MB)的大小为默认值,JDK版本为1.8.0_252。

在运行 TOP 命令时,它观察到我的 6.3 GB RAM 被 java 进程占用。

PR   NI    VIRT     RES    SHR S  %CPU %MEM   TIME+   COMMAND   
20   0  28.859g  6.341g  22544 S 215.2 83.1   4383:23 java    
Run Code Online (Sandbox Code Playgroud)

我尝试使用 JCMD、JMAP 和 JSTAT 命令分析 JVM 的本机内存。

JMAP -heap 命令的输出:

Debugger attached successfully.
Server compiler detected.
JVM version is 25.252-b14

using thread-local object allocation.
Garbage-First (G1) GC with 33 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 3670016000 (3500.0MB)
   NewSize                  = 1363144 (1.2999954223632812MB)
   MaxNewSize               = 2202009600 (2100.0MB)
   OldSize                  = 5452592 (5.1999969482421875MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 36700160 (35.0MB)
   MaxMetaspaceSize         = 419430400 (400.0MB)
   G1HeapRegionSize         = 1048576 (1.0MB)

Heap Usage:
G1 Heap:
   regions  = 3500
   capacity = 3670016000 (3500.0MB)
   used     = 1735444208 (1655.048568725586MB)
   free     = 1934571792 (1844.951431274414MB)
   47.28710196358817% used
G1 Young Generation:
Eden Space:
   regions  = 1311
   capacity = 2193620992 (2092.0MB)
   used     = 1374683136 (1311.0MB)
   free     = 818937856 (781.0MB)
   62.66730401529637% used
Survivor Space:
   regions  = 113
   capacity = 118489088 (113.0MB)
   used     = 118489088 (113.0MB)
   free     = 0 (0.0MB)
   100.0% used
G1 Old Generation:
   regions  = 249
   capacity = 1357905920 (1295.0MB)
   used     = 241223408 (230.04856872558594MB)
   free     = 1116682512 (1064.951431274414MB)
   17.76436824135799% used

485420 interned Strings occupying 83565264 bytes.
Run Code Online (Sandbox Code Playgroud)

JSTAT -gc 命令的输出:

 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
 0.0   33792.0  0.0   33792.0 1414144.0 1204224.0 2136064.0  1558311.7  262872.0 259709.5 19200.0 18531.5  22077  985.995  10     41.789 1027.785
 0.0   33792.0  0.0   33792.0 1414144.0 1265664.0 2136064.0  1558823.7  262872.0 259709.5 19200.0 18531.5  22077  985.995  10     41.789 1027.785
 0.0   63488.0  0.0   63488.0 124928.0 32768.0  3395584.0  1526795.8  262872.0 259709.5 19200.0 18531.5  22078  986.041  10     41.789 1027.830
 0.0   63488.0  0.0   63488.0 124928.0 49152.0  3395584.0  1526795.8  262872.0 259709.5 19200.0 18531.5  22078  986.041  10     41.789 1027.830
 0.0   63488.0  0.0   63488.0 124928.0 58368.0  3395584.0  1526795.8  262872.0 259709.5 19200.0 18531.5  22078  986.041  10     41.789 1027.830
Run Code Online (Sandbox Code Playgroud)

甚至“JCMD pid VM.native_memory summary”的输出产生的总和大约为 5.0GB,甚至不接近 6.3GB。所以我找不到余额 1.3GB 的用途。

我试图找出 6.3GB 是如何与 JVM 实际映射的。所以我决定检查 /proc/pid 文件夹。

在 /proc/pid/status 文件中,

VmRSS   : 6649680 kB 
RssAnon :   6627136 kB
RssFile :     22544 kB
RssShmem:         0 kB 
Run Code Online (Sandbox Code Playgroud)

由此我发现6.3GB的大部分空间都被匿名空间占用了。

PMAP 命令的输出(截断):

Address           Kbytes     RSS   Dirty Mode  Mapping
0000000723000000 3607296 3606076 3606076 rw---   [ anon ]
00000007ff2c0000   12544       0       0 -----   [ anon ]
00007f4584000000     132       4       4 rw---   [ anon ]
00007f4584021000   65404       0       0 -----   [ anon ]
00007f4588000000     132      12      12 rw---   [ anon ]
00007f4588021000   65404       0       0 -----   [ anon ]
00007f458c000000     132       4       4 rw---   [ anon ]
00007f458c021000   65404       0       0 -----   [ anon ]
00007f4590000000     132       4       4 rw---   [ anon ]
00007f4590021000   65404       0       0 -----   [ anon ]
00007f4594000000     132       8       8 rw---   [ anon ]
00007f4594021000   65404       0       0 -----   [ anon ]
00007f4598000000     132       4       4 rw---   [ anon ]
00007f4598021000   65404       0       0 -----   [ anon ]
00007f459c000000    2588    2528    2528 rw---   [ anon ]

Run Code Online (Sandbox Code Playgroud)

我发现第一个匿名地址可能映射到堆内存,因为它的大小为 3.4GB。但是,我无法找到匿名空间的其余部分是如何使用的。

我需要帮助找出 JVM 进程如何使用额外的 1.3 GB。

除了本机内存跟踪中提到的之外,任何有关 JVM 使用的内存的信息都将不胜感激。

apa*_*gin 5

正如这里所讨论的,除了本机内存跟踪涵盖的领域之外,JVM 进程中还有其他消耗内存的事情。

许多大小恰好为 64MB 的匿名区域(如您的pmap输出中所示)表明这些是malloc arenas。众所周知,标准 glibc 分配器存在内存使用过多的问题,尤其是在具有许多线程的应用程序中。我建议使用jemalloc(或tcmallocmimalloc)作为标准分配器的直接替代品 - 它没有提到的泄漏。另一种解决方案是使用环境变量限制 malloc 区域的数量MALLOC_ARENA_MAX

如果即使在切换到 后问题仍然存在jemalloc,这可能是本机内存泄漏的迹象。例如,Java 应用程序中的本机泄漏可能是由以下原因引起的

  • 未关闭的资源/流:ZipInputStreamDirectoryStreamInflaterDeflater等。
  • JNI 库和代理库,包括标准jdwp代理
  • 不正确的字节码检测

要查找泄漏源,您还可以使用jemalloc其内置的分析功能。但是,jemalloc无法展开 Java 堆栈跟踪。

async-profiler可以显示混合的 Java+本机堆栈。尽管 async-profiler 的主要目的是 CPU 和分配分析,但它也可以帮助查找Java 应用程序中的本机内存泄漏。

有关详细信息和更多示例,请参阅我的Java 进程的内存占用演示文稿

  • 如果您创建和销毁大量使用某些本机分配(如 Netty)的线程,MALLOC_ARENA_MAX 可以创造奇迹。我的情况非常相似,有大量 64MB 区域。设置 MALLOC_ARENA_MAX=4 消除了很多这些。这是一篇很好的文章:https://dzone.com/articles/troubleshooting-problems-with-native-off-heap-memo (2认同)