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

43
我们通常会处理由于堆或permgen大小配置问题而导致的OutOfMemoryError问题。但是,所有JVM内存并不都是permgen或堆。 就我所理解的而言,它也可能与线程/堆栈、本机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)

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交换空间
  • Swappiness=0
  • 堆消耗似乎已经适当调整
  • 仅在服务器上运行:Solr和一些监控工具
  • 平均响应时间正确
  • 有时会有异常长的暂停,长达20秒
我猜这些暂停可能是在交换堆上进行的完整GC,对吗? 为什么会有这么多交换空间? 我甚至不确定这是否是JVM使服务器交换,或者它是隐藏的我看不到的东西。也许是操作系统页面缓存?但不确定为什么操作系统会创建页面缓存条目,如果那会导致交换。
我正在考虑测试在一些流行的基于Java的存储/NoSQL中使用的mlockall技巧,例如ElasticSearch、Voldemort或Cassandra:检查使用mlockall使JVM/Solr不交换

编辑:

在这里,您可以看到最大堆、已使用堆(蓝色)、已使用交换空间(红色)。它们似乎有些相关。

Swap and Heap

我可以通过Graphite看到定期发生许多ParNew GC。还有一些CMS GC与图片中堆的显着减少相对应。

暂停似乎与堆的减少无关,但在10:00至11:30之间定期分布,所以我猜可能与ParNew GC有关。

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


那个大小有多少是虚拟内存,多少是常驻内存? - Peter Lawrey
正如Peter Lawrey所提到的,主机提供商是否保证虚拟机本身始终在RAM中而不是被物理交换出去? - t0r0X
为了消除任何疑虑:在我之前的评论中,“虚拟机”是指“虚拟服务器”==“您的操作系统正在运行的虚拟机器”。 - t0r0X
顺便问一下,你使用的是Oracle JVM还是其他的JVM,比如IBM JVM或JRockit(现在也是Oracle)? - t0r0X
4个回答

11

你的堆实际上使用了6.5 GB的虚拟内存(这可能包括永久代)

你有一堆线程使用了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 perm gen - 700 MB堆栈。 - <250个共享库 - 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任务管理器在内存计算方面存在许多已知问题。(也许在最新的操作系统中已经修复了) - Peter Lawrey
考虑到你只有8GB的内存,你可以确信没有任何进程使用超过8GB的内存;你有1.1GB的缓存文件和0.4GB的空闲内存。很可能你的程序或数据在tmpfs中被推到了交换分区。顺便说一句,我建议购买更多的内存,你可以花费约300美元购买32GB的内存。 - Peter Lawrey
我们可以增加内存数量,但实际上我更想理解问题而不是使用简单的解决方案。我们有一个良好的平均响应时间,但也有一些暂停,这可能对应于交换堆和完整GC。 - Sebastien Lorber
好的,谢谢。但是如果我的堆大小为6GB,并且内存映射文件和tmpfs文件没有被交换,为什么在“free”中显示有1.5GB的交换空间?我现在无法执行“df”,因为我在家。 - Sebastien Lorber
无法知道已经交换到磁盘的内容。如果您停止tmpfs中的进程或删除文件,并清除了一些交换空间,则知道它正在使用某些交换空间。无论如何,在我看来,这不值得担心这么少的内存,我的8岁孩子有一台8GB的电脑用于游戏。我会购买更多内存。 - Peter Lawrey
显示剩余12条评论

3
虽然Lawrey先生详细回答了您失去内存的位置和方式,但我认为拥有一些具体步骤会很有用(做这个做那个,你就知道你的Java内存去哪了)...
他的回答并没有真正帮助我解决类似的非堆内存使用问题,在我的情况下,这绝对不是线程问题。

enter image description here enter image description here

一个只使用了30MB堆空间,看起来完全健康的应用程序,无缘无故消耗了700%的非堆内存。最终Linux会杀死它,我也不知道为什么,在Eclipse内存分析器中也无法进行堆转储分析......
帮助我的工具叫做jxray。它不是免费的(好东西都不是),但有试用版。
1. 前往https://jxray.com/download获取该工具; 2. 获取一个堆转储文件(是的,我知道你想要非堆内存,但请先这样做); 3. 生成报告:./jxray.sh /path/to/dump
它将在你的内存转储旁边创建一个HTML文件报告,其中包含清晰明了的总结,显示了内存使用情况以及你的问题所在。
在我的情况下,它看起来像这样。

enter image description here

然后您可以放大问题并查看其源头。显然,该工具足够智能,可以查看直接字节缓冲区的分配大小,以了解您的应用程序使用的远远超过堆转储中的大小。

enter image description here

在我的情况下,我懒得为简单的长轮询HTTP请求使用okhttp,这是这个小应用程序的全部目的。显然,它非常缓慢地泄漏内存,我的应用程序会每隔几周死一次。现在我除掉了okhttp,将java升级到了13,并使用原生的http客户端,一切都正常工作了,我的类路径中还少了一个垃圾库。
我也建议在健康的应用程序上使用它,相信你会发现一些有趣的事实,你以前并不知道它们。

嘿,我知道这是一篇旧帖子,我也遇到了类似的内存泄漏问题,我想知道你是如何通过这份报告发现 okhttp 有问题的? 我在这些图片中没有看到任何相关的参考。 - jruivo
它在其他选项卡/屏幕中提供了所有路径,我从这个工具中找到了答案,只是没有在截图中显示出来。 - vach
它非常聪明地缩小范围并呈现给你有用的信息。 - vach
1
没有问题,它是一个很棒的工具,试试看吧。 - vach

1
使用 jpsjstat,您可以轻松跟踪Java程序内存的详细信息。
使用 jps 命令查找pid,并使用该pid获取所需Java进程的内存详细信息,例如:jstat $pid。如果需要,可以在循环中运行它们,以便更加紧密地监视所需的内存详细信息。
您可以在 github 上找到此想法的Bash实现。 它提供以下输出:
=====  ======  =======  =======  =====
 PID    Name   CurHeap  MaxHeap  %_CPU
=====  ======  =======  =======  =====
2777   Test3      1.26     1.26    5.8
2582   Test1      2.52     2.52    8.3
2562   Test2      2.52     2.52    6.4

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

PS
(已删除)

PPS 我记得我之前用过另一个工具:Visual GC。它可以以详细的视觉方式展示JVM内存管理中发生的情况,这里有一些截图。非常强大,甚至可以通过VisualVM插件集成(请参见VisualVM主页上的插件部分)。

PPPS
我们有时会出现异常长的暂停,长达20秒。[...] 我猜这些暂停可能是交换堆上的完整GC引起的?
是的,可能是这样。即使在非交换堆上,长时间的暂停也可能是由完整GC引起的。使用VisualVM,您可以监视在发生~20秒暂停的同时是否发生了完整GC。我建议在另一台主机上运行VisualVM,并通过显式JMX连接到您虚拟服务器上的JVM进程,以避免使用额外负载影响测量结果。您可以让该设置运行数天/数周,从而收集有关该现象的确定性信息。

根据当前信息,目前只有以下可能性:

  • 观察到的暂停与完整GC同时发生:JVM没有正确调优。您可以通过JVM参数来缓解此问题,也可以选择另一个GC算法/引擎(您尝试过CMS和G1 GC吗?有关此问题的更多信息,请参见此处
  • 观察到的暂停与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。但我不太清楚应该看哪里。 - Sebastien Lorber
VisualGC(以及带插件的VisualVM)可以显示有关JVM内存管理和涉及GC内存区域的内部信息。Java提供了一些GC引擎,它们使用不同类型的内存管理。如果您想真正了解JVM中发生的情况,必须使用这些工具。仅从外部使用pmap、statsd等过程参数根本没有帮助,您只会看到症状,而不是原因! - t0r0X
我不明白,你确定理解我的问题吗?没有堆消耗问题,GC已经调整好了,我已经使用了Yourkit(一种类似于VisualVM的工具),并且知道堆中发生了什么。这里的问题是:我的堆在哪里?在物理内存还是交换空间中。我不认为这些工具是合适的。 - Sebastien Lorber
我们已经调整了GC并看到了非常好的增加结果。我们不使用外部服务:我们运行具有500个VM的vSphere,并且没有SLA,所有机器都具有虚拟RAM = 物理RAM。 - Sebastien Lorber
在您附加到问题的最后一个图表中,暂停/冻结发生在哪里,持续多长时间?此外,在绿色和蓝色图表中有一个中断,大约在10:26-10:28左右,这个中断是什么意思? - t0r0X
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接