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

时间:2013-05-22 16:34:16

标签: java performance memory jvm

由于堆或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)

Tunning

-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,或者是隐藏的东西,我看不到。也许OS页面缓存?但不确定为什么操作系统会创建页面缓存条目,如果它创建交换。

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


修改

在这里你可以看到最大堆,用过的堆(蓝色),用过的交换(红色)。这似乎有点相关。

Swap and Heap

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

暂停似乎与堆减少没有关联,但是在10:00到11:30之间定期分配,所以它可能与我猜的ParNew GC有关。

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

4 个答案:

答案 0 :(得分:9)

你的堆实际上使用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库,否则我猜你有很多线程,每个线程都有自己的堆栈空间。我会检查你拥有的线程数。您可以减少每个线程的最大堆栈空间,但更好的选择可能是减少您拥有的线程数。

根据定义,off heap内存是不受管理的,因此不容易“调整”。即使调整堆也不简单。

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个......

答案 1 :(得分:1)

监视(和部分更改)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作为拇指的规则。

答案 2 :(得分:0)

使用jpsjstat,您只需跟踪java程序内存的详细信息。

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

您可以在github

上找到此想法的bash实现

答案 3 :(得分:0)

虽然劳里先生非常详细地回答了您丢失记忆的位置和方式, 我相信执行某些特定步骤(例如,这样做会很有用,您会知道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

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

我也建议在健康的应用程序上使用它,非常确定您会发现一些您不了解的有趣事实