监视 JVM 的非堆内存使用情况

新手上路,请多包涵

我们通常会处理 OutOfMemoryError 问题,因为堆或 permgen 大小配置问题。

但是所有的 JVM 内存都不是 permgen 或 heap。据我了解,它也可能与线程/堆栈、本机 JVM 代码有关……

但是使用 pmap 我可以看到进程分配了 9.3G,这是 3.3G 堆外内存使用。

我想知道监视和调整这种额外的堆外内存消耗的可能性有多大。

我不使用直接堆外内存访问(MaxDirectMemorySize 默认为 64m)

 Context: Load testing
Application: Solr/Lucene server
OS: Ubuntu
Thread count: 700
Virtualization: vSphere (run by us, no external hosting)

虚拟机

java version "1.7.0_09"
Java(TM) SE Runtime Environment (build 1.7.0_09-b05)
Java HotSpot(TM) 64-Bit Server VM (build 23.5-b02, mixed mode)

调整

-Xms=6g
-Xms=6g
-XX:MaxPermSize=128m

-XX:-UseGCOverheadLimit
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:+CMSClassUnloadingEnabled

-XX:+OptimizeStringConcat
-XX:+UseCompressedStrings
-XX:+UseStringCache

内存映射:

https://gist.github.com/slorber/5629214

虚拟机统计

procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa
 1  0   1743    381      4   1150    1    1    60    92    2    0  1  0 99  0

自由的

             total       used       free     shared    buffers     cached
Mem:          7986       7605        381          0          4       1150
-/+ buffers/cache:       6449       1536
Swap:         4091       1743       2348

最佳

top - 11:15:49 up 42 days,  1:34,  2 users,  load average: 1.44, 2.11, 2.46
Tasks: 104 total,   1 running, 103 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.5%us,  0.2%sy,  0.0%ni, 98.9%id,  0.4%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   8178412k total,  7773356k used,   405056k free,     4200k buffers
Swap:  4190204k total,  1796368k used,  2393836k free,  1179380k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
17833 jmxtrans  20   0 2458m 145m 2488 S    1  1.8 206:56.06 java
 1237 logstash  20   0 2503m 142m 2468 S    1  1.8 354:23.19 java
11348 tomcat    20   0 9184m 5.6g 2808 S    1 71.3 642:25.41 java
    1 root      20   0 24324 1188  656 S    0  0.0   0:01.52 init
    2 root      20   0     0    0    0 S    0  0.0   0:00.26 kthreadd
...

df -> tmpfs

 Filesystem                1K-blocks     Used Available Use% Mounted on
tmpfs                       1635684      272   1635412   1% /run


我们面临的主要问题:

  • 服务器有8G物理内存
  • Solr的heap只需要6G
  • 有1.5G的swap
  • 交换=0
  • 堆消耗似乎得到了适当的调整
  • 在服务器上运行:只有 Solr 和一些监控的东西
  • 我们有正确的平均响应时间
  • 我们有时会有异常长的停顿,最多 20 秒

我想暂停可能是交换堆上的完整 GC,对吗?

为什么有这么多交换?

我什至真的不知道这是使服务器交换的 JVM,还是我看不到的隐藏的东西。也许操作系统页面缓存?但不确定如果创建交换,操作系统为什么会创建页面缓存条目。

我正在考虑测试 mlockall 在一些流行的基于 Java 的存储/NoSQL(如 ElasticSearch、Voldemort 或 Cassandra)中使用的技巧:检查 Make JVM/Solr not swap, using mlockall


编辑:

在这里你可以看到最大堆,已用堆(蓝色),已用交换(红色)。好像有点关系

交换和堆

我可以用 Graphite 看到有很多 ParNew GC 定期发生。并且有一些 CMS GC 对应于图片的堆显着减少。

暂停似乎与堆减少无关,但有规律地分布在 10:00 到 11:30 之间,所以我猜它可能与 ParNew GC 有关。

在负载测试期间,我可以看到一些磁盘活动以及一些交换 IO 活动,当测试结束时,这些活动非常平静。

原文由 Sebastien Lorber 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.1k
2 个回答

您的堆实际上使用了 6.5 GB 的虚拟内存(这可能包括 perm gen)

您有一堆使用 64 MB 堆栈的线程。不清楚为什么有些人使用默认的 1 MB。

总共有 930 万 KB 的虚拟内存。我只会担心居民的大小。

尝试使用 top 来查找进程的驻留大小。

你可能会发现这个程序很有用

    BufferedReader br = new BufferedReader(new FileReader("C:/dev/gistfile1.txt"));
    long total = 0;
    for(String line; (line = br.readLine())!= null;) {
        String[] parts = line.split("[- ]");
        long start = new BigInteger(parts[0], 16).longValue();
        long end = new BigInteger(parts[1], 16).longValue();
        long size = end - start + 1;
        if (size > 1000000)
            System.out.printf("%,d : %s%n", size, line);
        total += size;
    }
    System.out.println("total: " + total/1024);


除非你有一个使用内存的 JNI 库,否则我猜你有很多线程,每个线程都有自己的堆栈空间。我会检查您拥有的线程数。您可以减少每个线程的最大堆栈空间,但更好的选择可能是减少您拥有的线程数。

根据定义,堆外内存是非托管的,因此不容易“调整”。即使调整堆也不简单。

64 位 JVM 上的默认堆栈大小为 1024K,因此 700 个线程将使用 700 MB 的虚拟内存。

您不应将虚拟内存大小与常驻内存大小混淆。 64 位应用程序上的虚拟内存几乎是空闲的,这只是您应该担心的常驻大小。

在我看来,您总共有 9.3 GB。

  • 6.0 GB 堆。
  • 128 MB 永久生成
  • 700 MB 堆栈。
  • < 250 个共享库
  • 2.2 GB 未知(我怀疑虚拟内存不是常驻内存)

上次有人遇到这个问题时,他们的线程比他们应该的多得多。我会检查您拥有的最大线程数,因为它是决定虚拟大小的峰值。例如,它接近 3000 吗?


嗯,每一对都是一个线程。

 7f0cffddf000-7f0cffedd000 rw-p 00000000 00:00 0
7f0cffedd000-7f0cffee0000 ---p 00000000 00:00 0

这些表明您现在的线程略少于 700…..

原文由 Peter Lawrey 发布,翻译遵循 CC BY-SA 3.0 许可协议

虽然 Lawrey 先生非常详细地回答了你在哪里以及如何失去记忆,但我相信有一些特定的步骤会很有用,比如(这样做,你就会知道你的 Java 内存去了哪里)……

他的回答并没有真正帮助我解决类似的堆外内存使用问题,就我而言,这绝对不是线程问题。

在此处输入图像描述在此处输入图像描述

仅使用 30mb 堆并且看起来非常健康的应用程序,无缘无故地消耗了 700% 以上的堆。最终 linux 会杀死它,我不知道为什么,没有堆转储分析帮助 eclipse 内存分析器……

帮助我解决问题的工具称为 jxray。它不是免费的(没什么好东西),但它有试用版。

  1. 前往 https://jxray.com/download 并获取该工具
  2. 获取堆转储(是的,我知道你想要关闭堆内存,但就这么做吧)
  3. 生成报告 ./jxray.sh /path/to/dump

它将在您的内存转储旁边创建一个 html 文件报告,该报告必须简要说明您的问题发生在哪里以及在哪里。

就我而言,它看起来像这样。

在此处输入图像描述

然后你可以放大问题并查看它的来源。显然,该工具足够聪明,可以查看直接字节缓冲区的分配大小,从而意识到您的应用程序使用的资源远远超过堆转储中的资源。

在此处输入图像描述

在我的例子中,我懒惰并使用 okhttp 进行简单的长轮询 http 请求,这是这个小应用程序的全部目的。显然它泄漏内存非常非常缓慢,我的应用程序每隔几周就会死一次。我摆脱了 okhttp,将 java 升级到 13 并使用了本机 http 客户端,现在一切正常,而且我的类路径中少了一个废话库。

我还建议您在健康的应用程序上使用它,相信您会发现一些您不知道的有趣事实)

原文由 vach 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题