由于堆或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
我们遇到的主要问题是:
我想暂停可能是交换堆上的完整GC吗?
为什么有这么多交换?
我甚至不知道这是否是使服务器交换的JVM,或者是隐藏的东西,我看不到。也许OS页面缓存?但不确定为什么操作系统会创建页面缓存条目,如果它创建交换。
我正在考虑测试一些流行的基于Java的存储/ NoSQL中使用的mlockall
技巧,如ElasticSearch,Voldemort或Cassandra:check Make JVM/Solr not swap, using mlockall
修改
在这里你可以看到最大堆,用过的堆(蓝色),用过的交换(红色)。这似乎有点相关。
我可以看到Graphite有很多ParNew GC定期发生。并且有一些CMS GC对应于图片的堆显着减少。
暂停似乎与堆减少没有关联,但是在10:00到11:30之间定期分配,所以它可能与我猜的ParNew GC有关。
在负载测试期间,我可以看到一些光盘活动以及一些交换IO活动,这在测试结束时非常平静。
答案 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。
最后一次有人遇到这个问题时,他们拥有的线程比他们应该的要多得多。我会检查你拥有的最大线程数,因为它是确定虚拟大小的峰值。例如它接近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拥有最新信息,目前只有这些可能性:
我应该提到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)
使用jps
和jstat
,您只需跟踪java程序内存的详细信息。
使用jps
命令查找pid并使用该pid使用jstat $pid
获取所需java进程的内存详细信息。如果需要,在循环中运行它们,您将能够密切监视所需的内存详细信息。
您可以在github
上找到此想法的bash实现答案 3 :(得分:0)
虽然劳里先生非常详细地回答了您丢失记忆的位置和方式, 我相信执行某些特定步骤(例如,这样做会很有用,您会知道Java内存的去向)...
他的回答并没有真正帮助我解决类似的堆外内存使用问题,对于我来说,这绝对不是线程问题。
仅使用30mb的堆并且看起来非常健康的应用程序,没有充分的理由就消耗了700%的堆空间。最终,Linux会杀死它,我不知道为什么,没有堆转储分析可以帮助Eclipse内存分析器...
帮助我解决问题的工具称为jxray。它不是免费的(没有什么好处),但是有一个试用版。
./jxray.sh /path/to/dump
它将在您的内存转储旁边创建一个html文件报告,该报告将简要说明问题所在以及问题所在。
就我而言,它看起来像这样。
然后,您可以放大问题并查看问题出处。显然,该工具很聪明,可以研究分配的直接字节缓冲区大小,以了解您的应用程序使用的内存远远超过堆转储中的内存。
在我的情况下,我很懒,并使用okhttp进行了简单的长轮询http请求,这是此小型应用程序的全部目的。显然,它泄漏内存的速度非常缓慢,我的应用程序每隔几周就会死一次。 我摆脱了okhttp,将java升级到13,并使用本机http客户端,现在一切正常,并且我的类路径中缺少一个废话库。
我也建议在健康的应用程序上使用它,非常确定您会发现一些您不了解的有趣事实