什么会消耗 Java 进程中的内存?

什么会消耗 Java 进程中的内存?

我们正在尝试调查中等负载下 Java 进程的内存使用情况。

  PID   USER    PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
  12663 test    20   0 8378m 6.0g 4492 S   43  8.4 162:29.95 java

如您所见,我们的常驻内存为 6Gb。现在有趣的部分是:该过程使用以下参数执行:

  • -Xmx2048m
  • -Xms2048m
  • -XX:新大小=512m
  • -XX:MaxDirectMemorySize=256m
  • ... 其他一些用于 GC 和其他东西

查看这些设置和实际内存使用情况,我们偶然发现该过程预期使用的内存和实际使用的内存之间存在差异。

通常我们的内存问题是通过分析堆转储来解决的,但在这种情况下我们的内存在堆之外的某个地方使用。

问题:尝试找出内存使用率如此之高的原因需要哪些步骤?哪些工具可以帮助我们确定该进程中哪些部分占用了内存?

編輯 0

这看起来不像是一个与堆相关的问题,因为那里仍然有相当多的空间:

jmap -heap 12663

结果是(已编辑以节省空间)

Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize      = 2147483648 (2048.0MB)
NewSize          = 536870912 (512.0MB)
MaxNewSize       = 536870912 (512.0MB)
OldSize          = 1610612736 (1536.0MB)
NewRatio         = 7
SurvivorRatio    = 8
PermSize         = 21757952 (20.75MB)
MaxPermSize      = 85983232 (82.0MB)

New Generation: 45.7% used
Eden Space: 46.3% used
From Space: 41.4% used
To Space: 0.0% used
concurrent mark-sweep generation: 63.7% used
Perm Generation: 82.5% used

编辑1

使用 pmap 我们可以看到有相当多的 64Mb 分配:

pmap -x 12663 | grep rwx | sort -n -k3 | less

结果是:

... a lot more of these 64Mb chunks
00007f32b8000000       0   65508   65508 rwx--    [ anon ] <- what are these?
00007f32ac000000       0   65512   65512 rwx--    [ anon ]
00007f3268000000       0   65516   65516 rwx--    [ anon ]
00007f3324000000       0   65516   65516 rwx--    [ anon ]
00007f32c0000000       0   65520   65520 rwx--    [ anon ]
00007f3314000000       0   65528   65528 rwx--    [ anon ] 
00000000401cf000       0  241904  240980 rwx--    [ anon ] <- Direct memory ?
000000077ae00000       0 2139688 2139048 rwx--    [ anon ] <- Heap ?

那么如何找出这些 64Mb 块是什么?什么在使用它们?它们里面有什么类型的数据?

谢谢

答案1

该问题可能与此有关glibc 问题

基本上,当您有多个线程分配内存时,glibc 将扩大可用竞技场的数量以进行分配,以避免锁争用。竞技场大小为 64Mb。上限是创建 8 倍于核心竞技场数量的竞技场。当线程访问已锁定的竞技场时,将按需创建竞技场,因此竞技场会随着时间的推移而增长。

在 Java 中,如果线程很多,这很快就会导致创建大量竞技场。这些竞技场到处都有分配。最初,每个 64Mb 竞技场只是映射的未提交内存,但随着分配,您开始为它们使用实际内存。

您的 pmap 可能有类似下面的列表。请注意 324K + 65212K = 65536K、560K + 64976K == 65536K、620K + 64916K == 65536K。也就是说,它们加起来是 64Mb。

00007f4394000000 324K rw--- [ 匿名 ]
00007f4394051000 65212K ----- [匿名]
00007f4398000000 560K rw--- [ 匿名 ]
00007f439808c000 64976K ----- [匿名]
00007f439c000000 620K rw--- [ 匿名 ]
00007f439c09b000 64916K ----- [匿名]

至于解决方法:该错误提到您可以设置一些环境参数来限制竞技场的数量,但您需要足够高的 glibc 版本。

答案2

怎么样波长探针? 除此以外,它还可以向您显示类似以下屏幕截图的内存使用情况细目:

Lambda Probe 内存使用情况视图

有时pmap -x your_java_pid也会有帮助。

答案3

JProfiler 可能是您想要的东西,但它不是免费的。另一个用于调查 Java 进程内存使用情况的优秀免费工具是 Java VisualVM,它是 Oracle/Sun JDK 发行版中的 JDK 工具。我个人建议采用更全面的方法解决问题(即监控 JDK + OS + 磁盘等)——使用一些网络监控系统 - Nagios、Verax NMS 或 OpenNMS。

答案4

JDK 中有一个方便的工具可以查看堆内存的分配,名为 jmap,除此之外,还有堆栈等(Xss)。运行这两个 jmap 命令可获取有关内存使用情况的更多信息:

jmap -heap <PID>
jmap -permstat <PID>

要获取更多信息,您可以使用 jconsole(也包含在 JDK 中)连接到该进程。但是 Jconsole 要求在应用程序中配置 JMX。

相关内容