我在运行于 Oracle Java 虚拟机上的 Ubuntu 服务器上安装了 Glassfish v4.0,并且 JVM 进程驻留内存大小(通过“top”命令获得)不断增长,直到 JVM 没有内存来创建新线程。
我拥有的:
- 配备 1GB RAM 和 1.4GHz 处理器 (1Core) 的 VPS 服务器
- Ubuntu 服务器 12.04
- Java(TM) SE 运行时环境(版本 1.7.0_51-b13)
- Java HotSpot(TM) 64 位服务器 VM(版本 24.51-b03,混合模式)
- Glassfish v4.0 运行我的 Java EE Web 应用程序
- VM 使用以下参数运行 -XX:MaxPermSize=200m -XX:PermSize=100m -XX:Xmx=512m (如果相关我可以添加所有)
有什么问题:
Ram 使用率(res 内存)一直在增长,取决于每小时 10-100m 的使用率,直到 jvm 无法分配本机内存。
我尝试过什么:
- 我已经降低了最大堆空间,这只能节省时间,直到 jvm 崩溃
- 我已经连接了水管 (https://portal.plumbr.eu/)未检测到堆中的任何内存泄漏
- 我还将最大永久大小设置为较低的值。
我希望我的 JVM 能够稳定运行,因为我测量了堆空间 + perm gen 只占用了 400-600 mb,而“top”命令显示 java 进程内存增长到 850mb 然后就终止了。我知道 JVM 需要比 perm 空间和堆更多的内存,但你认为我还是为堆空间和 perm gen 提供了太多内存吗?任何帮助或指南都将不胜感激。
日志输出:http://pakers.lv/logs/hs_err_pid970.log 所有 JVM 燃料消耗:http://pakers.lv/logs/jvm_flags.txt
更新
我还尝试了什么(基于建议和我自己的发现):
- 我已将堆空间减少并固定为 256m,然后在系统仍然稳定时将其增加,我注意到我的系统上可以承受的最大堆是 512m 和 128m 永久代空间。(-Xmx512m、-Xms512m、-XX:PermSize=128m、-XX:MaxPermSize=128m)
- 减少了 Java 线程大小 -Xss256k,我无法将其减少到 218k 以下(jvm 无法启动)
- 添加 -D64 以便 jvm 在 64 位模式下运行
- 添加了 -XX:+AggressiveOpts(用于启用性能优化)、-XX:+UseCompressedOops(用于减少堆空间内存使用量)、-server 标志以在服务器模式下启动 jvm
- 由于我的堆空间大小非常有限,我修改了 NewRatio 以获得更大的永久代(堆空间的 1/3)-XX:NewRatio=3
- 添加了 GC 的诊断选项,以便我可以检查 OOM 错误 -XX:+PrintTenuringDistribution -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+HeapDumpOnOutOfMemoryError -Xloggc:/home/myuser/garbage.log
当前状态 通过这些更改,我终于限制了 Java 进程的常驻内存(RAM 使用量),这是我的目标。在我的例子中,512m 堆空间 + 128m 永久代空间导致 Java 进程的常驻内存约为 750m,这是稳定的。尽管我仍然有内存问题 - 堆内存不时会变满,并导致 Web 应用程序由于持续的垃圾收集而冻结,但操作系统不会终止该进程。所以我现在需要增加系统的可用内存(RAM)或检查堆使用情况并降低应用程序的占用空间。由于我的 Web 应用程序基于 Java EE(带有 EJB),我可能无法显着减少它。无论如何,感谢您的建议,如果有任何其他建议,请随时分享。
答案1
根据您所分享的内容,有几种可能性,例如:
- 存在漏洞的 JNI 库,或者
- 线程创建泄漏,或
- 泄漏的动态代码代理(perm-gen 泄漏),
但我只能猜测,因为您没有提供任何日志输出,也没有指出 JVM 是否抛出了OutOfMemoryException
(OOM),或者是否遇到了其他错误。您也没有提到正在使用哪种垃圾收集器,但如果上面显示的标志是唯一正在使用的 JVM 选项,那么它就是 CMS 收集器。
第一步是通过添加以下标志使垃圾收集器的操作可观察:
-XX:+PrintTenuringDistribution
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+HeapDumpOnOutOfMemoryError
-Xloggc:/path/to/garbage.log
如果确实是 OOM,您可以使用 VisualVM 或类似工具分析堆转储。我还使用 VisualVM 通过 JMX 现场监控 GC 操作。可以通过以下 JVM 标志启用对 JVM 内部的可见性:
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=4231
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false
其他资源:
- http://www.oracle.com/technetwork/java/javase/memleaks-137499.html
- http://javabook.compuware.com/content/memory/problem-patterns/memory-leaks.aspx
- https://visualvm.java.net/heapdump.html
- http://www.marcsturlese.com/2009/05/09/analyzing-java-heaps-with-jmap-and-jhat/
更新
日志确实有帮助。谢谢。该特定日志显示,在堆增长到配置的最大值之前,物理内存已耗尽。它尝试 malloc ~77M,但只剩下 ~63M 的物理内存:
本机内存分配(malloc)无法分配 77873152 字节用于提交保留内存。
..
/proc/meminfo:内存总量:1018724 kB 可用内存:63048 kB
我会这么做:
减少堆,使其“适合”机器。将最小和最大堆设置为相同的值,这样您就可以知道它是否能立即适合 - 如果不适合,它将无法启动。
您可以减少 Java 堆栈大小 (
-Xss
),但这个东西似乎不会产生很多线程,因此节省的内存不会超过一两兆字节。我认为 64 位 Linux 的默认值是 256k。如果将其减少太多,堆栈分配就会开始出现 OOM。重复测试。
当它在负载下运行一小段时间后,生成一个按需堆转储,以便使用进行差异诊断
jmap -dump:file=path_to_file <pid>
。应该会发生以下两种情况之一:(a) 如果存在泄漏,它最终会再次失败,但 OOM 的类型应该不同,或者 (b) 没有泄漏,这样 GC 就会更加努力,你就完了。鉴于你之前尝试过,前一种情况是可能的,除非你减少的最大大小也不适合。
如果确实发生了 OOM,请比较两个转储以查看使用
jhat
或其他堆分析器增长了什么。
祝你好运!
答案2
-D64
尝试通过向 JVM 启动标志添加以下内容以 64 位模式运行该进程。
您可以运行pmap $JVMPID
以查看虚拟内存的分配情况。在崩溃之前运行它。