[英]Monitor non-heap memory usage of a JVM
由于堆或永久代大小配置问题,我们通常会处理 OutOfMemoryError 问题。
但是所有的 JVM 内存都不是永久代或堆。 据我了解,它也可以与线程/堆栈、原生 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
我们面临的主要问题:
我想暂停可能是交换堆上的完整 GC,对吗?
为什么会有这么多的交换?
我什至不知道这是否是使服务器交换的 JVM,或者它是否是我看不到的隐藏内容。 也许操作系统页面缓存? 但不确定为什么操作系统会在创建交换时创建页面缓存条目。
我正在考虑测试在一些流行的基于 Java 的存储/NoSQL(如 ElasticSearch、Voldemort 或 Cassandra)中使用的mlockall
技巧:检查Make JVM/Solr not swap, using mlockall
编辑:
在这里您可以看到最大堆、已使用的堆(蓝色)、已使用的交换(红色)。 好像有点关系。
我可以通过 Graphite 看到有许多 ParNew GC 定期发生。 并且有一些CMS GC对应于图片的堆显着减少。
暂停似乎与堆减少无关,但在 10:00 和 11:30 之间有规律地分布,所以我猜它可能与 ParNew GC 有关。
在负载测试期间,我可以看到一些磁盘活动和一些交换 IO 活动,这在测试结束时非常平静。
您的堆实际上使用了 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。
上次有人遇到这个问题时,他们的线程比他们应该的多得多。 我会检查您拥有的最大线程数,因为它是决定虚拟大小的峰值。 例如它是否接近 3000?
嗯,这些对中的每一对都是一个线程。
7f0cffddf000-7f0cffedd000 rw-p 00000000 00:00 0
7f0cffedd000-7f0cffee0000 ---p 00000000 00:00 0
这些表明您现在的线程数略少于 700.....
监控(和部分更改)JVM 实例的运行时参数的一种非常方便的方法是 VisualVM:
聚苯乙烯
(已删除)
PPS 想起前段时间用的另一个工具: Visual GC 。 它直观地向您展示了 JVM 内存管理内部发生的事情,这里有一些屏幕截图。 非常强大,它甚至可以与 VisualVM 中的插件集成(请参阅 VisualVM 主页上的插件部分)。
购买力平价We sometimes have anormaly long pauses, up to 20 seconds. [...] I guess the pauses could be a full GC on a swapped heap right?
是的,那可能是。 即使在非交换堆上,full GC 也可能导致长时间的停顿。 使用 VisualVM,您可以监控在大约 20 秒的暂停发生时是否发生了完整的 GC。 我建议在另一台主机上运行 VisualVM 并通过显式 JMX将其连接到虚拟服务器上的 JVM 进程,以免因额外负载而伪造测量结果。 您可以让该设置运行数天/数周,从而收集有关该现象的明确信息。
根据当前信息,目前只有这些可能性:
我应该提到 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 来自何处,请查看相关问题。 我个人使用系数 x1.5 作为经验法则。
虽然 Lawrey 先生非常详细地回答了您在哪里以及如何丢失内存,但我相信有一些特定的步骤会很有用(这样做,您将知道您的 Java 内存去哪里了)...
他的回答并没有真正帮助我解决类似的堆外内存使用问题,就我而言,这绝对不是线程问题。
仅使用 30mb 堆并且看起来非常健康的应用程序,无缘无故地消耗了 700% 的堆外。 最终 linux 会杀死它,我不知道为什么,没有堆转储分析对 eclipse 内存分析器有帮助......
帮助我的工具叫做 jxray。 它不是免费的(没有什么好处),但它有一个试用版。
./jxray.sh /path/to/dump
它将在您的内存转储旁边创建一个 html 文件报告,该报告必须总结出您的问题在哪里以及您的问题在哪里。
就我而言,它看起来像这样。
然后你可以放大问题,看看它来自哪里。 显然,该工具足够聪明,可以查看直接字节缓冲区的分配大小,以意识到您的应用程序使用的远远超过堆转储中的使用量。
就我而言,我很懒惰并使用 okhttp 进行简单的长轮询 http 请求,这是这个小应用程序的全部目的。 显然它非常非常缓慢地泄漏内存,我的应用程序每隔几周就会死一次。 我摆脱了 okhttp,将 java 升级到 13 并使用本机 http 客户端,现在一切正常,而且我的类路径中少了一个垃圾库。
我还建议在您的健康应用程序中使用它,很确定您会发现一些您不知道的有趣事实)
使用jps
和jstat
您可以简单地跟踪 Java 程序内存的详细信息。
使用jps
命令查找 pid 并使用该 pid 使用jstat $pid
获取所需 java 进程的内存详细信息。 如果需要,循环运行它们,您将能够密切监视所需的内存详细信息。
你可以在github上找到这个想法的 bash 实现
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.