监视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

Memory maps:

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

free

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

Top

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吗?

Why is there so much swap?

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

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


Edit:

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

Swap and Heap

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

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

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

回答(3)

3 years ago

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

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

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

3 years ago

你的堆实际上使用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个......

3 years ago

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

PS
(删除)

PPS我记得我前一段时间使用过的另一个工具:Visual GC . 它直观地向您展示了JVM内存管理中发生的事情,这里有一些screenshots . 非常强大,它甚至可以与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?
是的,那可能是 . 即使在非交换堆上,也可能由完整的GC引起长时间的暂停 . 使用VisualVM,您可以监视在发生~20秒暂停时是否发生完整GC . 我建议在另一台主机上运行VisualVM,并通过它将其连接到虚拟服务器上的JVM进程explicit JMX,为了不用额外的负载伪造测量值 . 您可以将该设置保持运行数天/周,从而收集有关该现象的确切信息 .

有现有信息的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作为拇指的规则 .