【问题标题】:Monitor non-heap memory usage of a JVM监控 JVM 的非堆内存使用情况
【发布时间】:2013-05-17 19:43:16
【问题描述】:

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

但是所有的 JVM 内存都不是 permgen 或 heap。 据我了解,也可以和Threads/Stacks、native JVM code有关……

但是使用 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)

JVM

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

vmstat

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的堆只占用6G
  • 有1.5G的swap
  • 交换=0
  • 似乎适当调整了堆消耗
  • 在服务器上运行:只有 Solr 和一些监控的东西
  • 我们有正确的平均响应时间
  • 我们有时会出现异常长的停顿,最长可达 20 秒

我猜暂停可能是交换堆上的完整 GC 对吧?

为什么会有这么多交换?

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

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


编辑:

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

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

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

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

【问题讨论】:

  • 该大小中有多少是虚拟内存,有多少是常驻内存?
  • 正如 Peter Lawrey 所提到的,主机提供商是否向您保证虚拟机本身始终位于 RAM 中而不是物理换出?
  • 为了消除任何疑虑:在我之前对“虚拟机”的评论中,我的意思是“虚拟服务器”==“您的操作系统正在运行的虚拟机”。
  • 顺便说一句,您使用 Oracle JVM 还是其他的,例如IBM JVM 还是 JRockit(现在也是 Oracle)?

标签: java performance memory jvm


【解决方案1】:

您的堆实际上使用了 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 堆栈。
  • 2.2 GB 未知(我怀疑虚拟内存不是常驻内存)

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


嗯,这些对中的每一对都是一个线程。

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

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

【讨论】:

  • 700 会使用相当多的内存,但不会使用 3.3 GB。你能列出进程的 /proc/{id}/mmap 吗? (或者是 Windows)顺便说一句,Windows 任务管理器在内存记帐方面存在许多已知问题。 (也许它们在最新的操作系统中已修复)
  • 可能,我现在在一个 Windoz 盒子上。
  • 看来 9.3 GB 是虚拟内存大小,而不是常驻大小。
  • 假设您只有 8 GB 的内存,您可以确定没有进程使用超过 8 GB 的内存;) 您有 1.1 Gb 的缓存文件和 0.4 GB 的可用内存。很可能您在 tmpfs 中有程序或数据已被推出以进行交换。顺便说一句,我会考虑购买更多内存。你可以花大约 300 美元获得 32 GB。
  • 我们可能会增加内存的数量,但实际上我想了解而不是使用简单的解决方案。我们的平均响应时间不错,但也有一些暂停,这可能对应于交换的堆和完整的 GC。
【解决方案2】:

监控(和部分更改)JVM 实例的运行时参数的一种非常方便的方法是 VisualVM:

PS
(已删除)

PPS 我记得我前段时间使用的另一个工具:Visual GC。它直观地向您详细展示了 JVM 内存管理内部发生的情况,这里有一些 screenshots。非常强大,甚至可以与 VisualVM 中的插件集成(参见 VisualVM 主页上的插件部分)。

PPPS
We sometimes have anormaly long pauses, up to 20 seconds. [...] I guess the pauses could be a full GC on a swapped heap right?
是的,可能是这样。即使在非交换堆上,这种长时间的停顿也可能是由完全 GC 引起的。使用 VisualVM,您可以监控在大约 20 秒的暂停发生时是否发生了完整的 GC。我建议在另一台主机上运行 VisualVM 并通过 explicit JMX 将其连接到虚拟服务器上的 JVM 进程,以免因额外负载而篡改测量结果。您可以让该设置运行数天/数周,从而收集有关该现象的确切信息。

掌握当前信息的Afaics,目前只有这些可能性:

  • 观察到的暂停与完全 GC 同时发生:JVM 未正确调整。您可以通过 JVM 参数来缓解这种情况,也可以选择其他 GC 算法/引擎(您是否尝试过 CMS and G1 GC?更多关于这是如何发生的信息,例如 here
  • 观察到的暂停与 JVM 中的完整 GC 不一致:物理虚拟主机可能是原因。验证您的 SLA(保证物理 RAM 中有多少虚拟 RAM)并联系您的服务提供商,要求监控虚拟服务器。

我应该提到 VisualVM 是随 Java 一起发布的。还有 JConsole,它也随 Java 一起提供,它比 VisualVM 更轻巧、更紧凑(但没有插件、没有分析等),但提供了类似的概述。

如果暂时为 VisualVM/JConsole/VisualGC 设置 JMX 连接过于复杂,您可以求助于以下 java 参数:-XX:+PrintGC -XX:+PrintGCTimeStamps -Xloggc:/my/log/path/gclogfile.log。这些参数将导致 JVM 将每次 GC 运行的条目写入指定的日志文件。此选项也非常适合长期分析,并且可能是 JVM 开销最小的选项。

在再次(又一次)思考您的问题后:如果您想知道额外的 3+ GB 来自哪里,请点击related question。我个人使用 x1.5 作为经验法则。

【讨论】:

  • 这是一个运行在 Ubuntu 上的负载测试环境。我们有一些监控工具,如 Graphite、Statsd、collectd、NewRelic,我们也使用 Yourkit。但我真的不知道在哪里看
  • VisualGC(和带有插件的 VisualVM)向您显示有关 JVM 内存管理和所涉及的 GC 内存区域的内部信息。 Java 提供了一些 GC 引擎,它们使用不同类型的内存管理。如果您想真正了解 JVM 中发生了什么,您必须使用这些工具。仅从 外部 使用 pmap、statsd 等查看过程参数根本没有帮助,您只会看到症状,而不是原因!
  • 我不明白,你确定明白我的问题吗?没有堆消耗问题,GC 已经调好,并且我已经使用了 Yourkit(这是某种类 VisualVM 的类固醇)并且知道堆中发生了什么。这里的问题是:我的堆在哪里?在物理内存上或交换。我认为这些工具不合适
  • 好的,现在我明白你的问题了。事实上,我提到的工具不会告诉你操作系统对内存页做了什么。
  • 我们已经调整了 GC 并看到了非常好的增加结果。我们不使用外部服务:我们使用 500 个虚拟机运行 vSphere,并且没有 SLA,所有机器都有虚拟 RAM = 物理 RAM
【解决方案3】:

虽然 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 客户端,现在一切正常,我的类路径中少了一个垃圾库。

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

【讨论】:

    【解决方案4】:

    使用jpsjstat,您可以简单地跟踪您的java 程序内存的详细信息。

    使用jps 命令查找pid,并使用该pid 使用jstat $pid 获取所需Java 进程的内存详细信息。如果需要,循环运行它们,您将能够密切监视所需的内存详细信息。

    您可以在 github 上找到这个想法的 bash 实现

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-10-10
      • 2012-03-15
      • 2011-04-30
      • 2016-07-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多