Allocated 1953546736 bytes of native memory before running out
针对在 32 位 Windows 上运行的 Sun 和 IBM Java 运行时的每次演示,其输出都已被捕获。提供的二进制文件已在以下操作系统上进行了测试:
Linux x86
Linux PPC 32
Linux 390 31
Windows x86
使用以下 Sun Java 运行时版本捕获输出:
java version "1.5.0_11"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_11-b03)
Java HotSpot(TM) Client VM (build 1.5.0_11-b03, mixed mode)
使用的 IBM Java 运行时版本为:
java version "1.5.0"
Java(TM) 2 Runtime Environment, Standard Edition (build pwi32devifx-20071025 (SR
6b))
IBM J9 VM (build 2.3, J2RE 1.5.0 IBM J9 2.3 Windows XP x86-32 j9vmwi3223-2007100
7 (JIT enabled)
J9VM - 20071004_14218_lHdSMR
JIT - 20070820_1846ifx1_r8
GC - 200708_10)
JCL - 20071025
在耗尽本机内存时尝试启动线程
com.ibm.jtc.demos.StartingAThreadUnderNativeStarvation 类尝试在耗尽进程地址空间时启动一个线程。这是发现 Java 进程已耗尽内存的一种常用方式,因为许多应用程序都会在其整个生存期启动线程。
当在 IBM Java 运行时上运行时,StartingAThreadUnderNativeStarvation 演示的输出如下:
Allocated 1019394912 bytes of native memory before running out
JVMDUMP006I Processing Dump Event "systhrow", detail
"java/lang/OutOfMemoryError" - Please Wait.
JVMDUMP007I JVM Requesting Snap Dump using 'C:\Snap0001.20080323.182114.5172.trc'
JVMDUMP010I Snap Dump written to C:\Snap0001.20080323.182114.5172.trc
JVMDUMP007I JVM Requesting Heap Dump using 'C:\heapdump.20080323.182114.5172.phd'
JVMDUMP010I Heap Dump written to C:\heapdump.20080323.182114.5172.phd
JVMDUMP007I JVM Requesting Java Dump using 'C:\javacore.20080323.182114.5172.txt'
JVMDUMP010I Java Dump written to C:\javacore.20080323.182114.5172.txt
JVMDUMP013I Processed Dump Event "systhrow", detail "java/lang/OutOfMemoryError".
java.lang.OutOfMemoryError: ZIP006:OutOfMemoryError, ENOMEM error in ZipFile.open
at java.util.zip.ZipFile.open(Native Method)
at java.util.zip.ZipFile.<init>(ZipFile.java:238)
at java.util.jar.JarFile.<init>(JarFile.java:169)
at java.util.jar.JarFile.<init>(JarFile.java:107)
at com.ibm.oti.vm.AbstractClassLoader.fillCache(AbstractClassLoader.java:69)
at com.ibm.oti.vm.AbstractClassLoader.getResourceAsStream(AbstractClassLoader.java:113)
at java.util.ResourceBundle$1.run(ResourceBundle.java:1101)
at java.security.AccessController.doPrivileged(AccessController.java:197)
at java.util.ResourceBundle.loadBundle(ResourceBundle.java:1097)
at java.util.ResourceBundle.findBundle(ResourceBundle.java:942)
at java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:779)
at java.util.ResourceBundle.getBundle(ResourceBundle.java:716)
at com.ibm.oti.vm.MsgHelp.setLocale(MsgHelp.java:103)
at com.ibm.oti.util.Msg$1.run(Msg.java:44)
at java.security.AccessController.doPrivileged(AccessController.java:197)
at com.ibm.oti.util.Msg.<clinit>(Msg.java:41)
at java.lang.J9VMInternals.initializeImpl(Native Method)
at java.lang.J9VMInternals.initialize(J9VMInternals.java:194)
at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:764)
at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:758)
at java.lang.Thread.uncaughtException(Thread.java:1315)
K0319java.lang.OutOfMemoryError: Failed to fork OS thread
at java.lang.Thread.startImpl(Native Method)
at java.lang.Thread.start(Thread.java:979)
at com.ibm.jtc.demos.StartingAThreadUnderNativeStarvation.main(
StartingAThreadUnderNativeStarvation.java:22)
调用 java.lang.Thread.start() 来尝试为一个新的操作系统线程分配内存。此尝试会失败并抛出 OutOfMemoryError。JVMDUMP 行通知用户 Java 运行时已经生成了标准的OutOfMemoryError 调试数据。
尝试处理第一个 OutOfMemoryError 会导致第二个错误 —— :OutOfMemoryError, ENOMEM error in ZipFile.open。当本机进程内存耗尽时通常会抛出多个OutOfMemoryError。Failed to fork OS thread 可能是在耗尽本机内存时最常见的消息。
本文提供的示例会触发一个 OutOfMemoryError 集群,这比您在自己的应用程序中看到的情况要严重得多。这一定程度上是因为几乎所有本机内存都已被使用,与实际的应用程序不同,使用的内存不会在以后被释放。在实际应用程序中,当抛出OutOfMemoryError 时,线程会关闭,并且可能会释放一部分本机内存,以让运行时处理错误。测试案例的这个细微特性还意味着,类库的许多部分(比如安全系统)未被初始化,而且它们的初始化受尝试处理内存耗尽情形的运行时驱动。在实际应用程序中,您可能会看到显示了很多错误,但您不太可能在一个位置看到所有这些错误。
在 Sun Java 运行时上执行相同的测试案例时,会生成以下控制台输出:
Allocated 1953546736 bytes of native memory before running out
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:574)
at com.ibm.jtc.demos.StartingAThreadUnderNativeStarvation.main(
StartingAThreadUnderNativeStarvation.java:22)
Allocated 1019481472 bytes of native memory before running out
JVMDUMP006I Processing Dump Event "uncaught", detail
"java/lang/OutOfMemoryError" - Please Wait.
JVMDUMP007I JVM Requesting Snap Dump using 'C:\Snap0001.20080324.100721.4232.trc'
JVMDUMP010I Snap Dump written to C:\Snap0001.20080324.100721.4232.trc
JVMDUMP007I JVM Requesting Heap Dump using 'C:\heapdump.20080324.100721.4232.phd'
JVMDUMP010I Heap Dump written to C:\heapdump.20080324.100721.4232.phd
JVMDUMP007I JVM Requesting Java Dump using 'C:\javacore.20080324.100721.4232.txt'
JVMDUMP010I Java Dump written to C:\javacore.20080324.100721.4232.txt
JVMDUMP013I Processed Dump Event "uncaught", detail "java/lang/OutOfMemoryError".
Exception in thread "main" java.lang.OutOfMemoryError:
Unable to allocate 1048576 bytes of direct memory after 5 retries
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:167)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:303)
at com.ibm.jtc.demos.DirectByteBufferUnderNativeStarvation.main(
DirectByteBufferUnderNativeStarvation.java:29)
Caused by: java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:154)
... 2 more
在此场景中,抛出了 OutOfMemoryError,它会触发默认的错误文档。OutOfMemoryError 到达主线程堆栈的顶部,并在stderr 上输出。
当在 Sun Java 运行时上运行时,此测试案例生成以下控制台输出:
Allocated 1953546760 bytes of native memory before running out
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:99)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)
at com.ibm.jtc.demos.DirectByteBufferUnderNativeStarvation.main(
DirectByteBufferUnderNativeStarvation.java:29)
1STGCHTYPE GC History
3STHSTTYPE 09:59:01:632262775 GMT j9mm.83 - Forcing J9AllocateObject()
to fail due to excessive GC
1STGCHTYPE GC History
3STHSTTYPE 09:59:01:632262775 GMT j9mm.84 - Forcing
J9AllocateIndexableObject() to fail due to excessive GC
当 Sun 实现耗尽 Java 堆内存时,它使用异常消息来显示它耗尽的是 Java 堆:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
IBM 和 Sun 实现都拥有一个详细的 GC 选项,用于在每个 GC 周期生成显示堆填充情况的跟踪数据。此信息可使用工具(比如 IBM Monitoring and Diagnostic Tools for Java - Garbage Collection and Memory Visualizer (GCMV))来分析,以显示 Java 堆是否在增长(参见参考资料)。
测量本机堆使用情况
如果您确定内存耗尽情况不是由 Java 堆耗尽引起的,那么下一步就是分析您的本机内存使用情况。
Windows 提供的 PerfMon 工具可用于监控和记录许多操作系统和进程指标,包括本机内存使用(参见
参考资料)。它允许实时跟踪计数器,或将其存储在日志文件中以供离线查看。使用 Private Bytes 计数器显示总体地址空间使用情况。如果显示值接近于用户空间的限制(前面已经讨论过,介于 2 到 3GB 之间),您应该会看到本机内存耗尽情况。
Linux 没有类似于 PerfMon 的工具,但是它提供了几个替代工具。命令行工具(比如 ps、top 和pmap)能够显示应用程序的本机内存占用情况。尽管获取进程内存使用情况的实时快照非常有用,但通过记录内存随时间的使用情况,您能够更好地理解本机内存是如何被使用的。为此,能够采取的一种方式是使用 GCMV。
GCMV 最初编写用于分析冗长的 GC 日志,允许用户在调优垃圾收集器时查看 Java 堆使用情况和 GC 性能的变化。GCMV 后来进行了扩展,支持分析其他数据源,包括 Linux 和 AIX 本机内存数据。GCMV 是作为 IBM Support Assistant (ISA) 的插件发布的。
要使用 GCMV 分析 Linux 本机内存配置文件,您首先必须使用脚本收集本机内存数据。GCMV 的 Linux 本机内存分析器通过根据时间戳隔行扫描的方式,读取 Linuxps 命令的输出。GCMV 提供了一个脚本来帮助以正确形式记录收集数据。要找到该脚本:
下载并安装 ISA Version 4(或更高版本),然后安装 GCMV 工具插件。
启动 ISA。
从菜单栏单击 Help >> Help Contents,打开 ISA 帮助菜单。
在左侧窗格的 Tool:IBM Monitoring and Diagnostic Tools for Java - Garbage Collection and Memory Visualizer >> Using the Garbage Collection and Memory Visualizer >> Supported Data Types >> Native memory >> Linux native memory 下找到 Linux 本机内存说明。
图 5 显示了该脚本在 ISA 帮助文件中的位置。如果您的帮助文件中没有 GCMV Tool 条目,很可能是因为您没有安装 GCMV 插件。
图 5. Linux 本机内存数据捕获脚本在 ISA 帮助对话框中的位置
GCMV 帮助文件中提供的脚本使用的 ps 命令仅适用于最新的 ps 版本。在一些旧的 Linux 分发版中,帮助文件中的命令将会生成错误信息。要查看您的 Linux 分发版上的行为,可以尝试运行ps -o pid,vsz=VSZ,rss=RSS。如果您的
ps 版本支持新的命令行参数语法,那么得到的输出将类似于:
PID VSZ RSS
5826 3772 1960
5675 2492 760
如果您的 ps 版本不支持新语法,得到的输出将类似于:
PID VSZ,rss=RSS
5826 3772
5674 2488
如果您在一个较老的 ps 版本上运行,可以修改本机内存脚本,将
ps -p $PID -o pid,vsz=VSZ,rss=RSS
行替换为
ps -p $PID -o pid,vsz,rss
将帮助面板中的脚本复制到一个文件中(在本例中名为 memscript.sh),找到您想要监控的 Java 进程的进程 ID (PID)(本例中为 1234)并运行:
./memscript.sh 1234 > ps.out
这会把本机内存日志写入到 ps.out 中。要分析内存使用情况:
在 ISA 中,从 Launch Activity 下拉菜单选择 Analyze Problem。
选择接近 Analyze Problem 面板顶部的 Tools 标签。
选择 IBM Monitoring and Diagnostic Tools for Java - Garbage Collection and Memory Visualizer.
// _NT_SYMBOL_PATH set by default to C:\WINDOWS\symbols
//
// Each log entry has the following syntax:
//
// + BYTES_DELTA (NEW_BYTES - OLD_BYTES) NEW_COUNT allocs BackTrace TRACEID
// + COUNT_DELTA (NEW_COUNT - OLD_COUNT) BackTrace TRACEID allocations
// ... stack trace ...
//
// where:
//
// BYTES_DELTA - increase in bytes between before and after log
// NEW_BYTES - bytes in after log
// OLD_BYTES - bytes in before log
// COUNT_DELTA - increase in allocations between before and after log
// NEW_COUNT - number of allocations in after log
// OLD_COUNT - number of allocations in before log
// TRACEID - decimal index of the stack trace in the trace database
// (can be used to search for allocation instances in the original
// UMDH logs).
//
+ 412192 ( 1031943 - 619751) 963 allocs BackTrace00468
Total increase == 412192
--trace-children=yes 选项使 Valgrind 跟踪由 Java 启动器启动的任何进程。一些 Java 启动器版本会重新执行其本身(它们从头重新启动其本身,再次设置环境变量来改变行为)。如果您未指定--trace-children,您将不能跟踪实际的 Java 运行时。
--leak-check=full 选项请求在代码运行结束时输出对泄漏的代码区域的完整堆栈轨迹,而不只是汇总内存的状态。
当该命令运行时,Valgrind 输出许多警告和错误(在此环境中,其中大部分都是无意义的),最后按泄漏的内存量升序输出存在泄漏的调用堆栈。在 Linux x86 上,针对LeakyJNIApp 的 Valgrind 输出的汇总部分结尾如下:
==20494== 8,192 bytes in 8 blocks are possibly lost in loss record 36 of 45
==20494== at 0x4024AB8: malloc (vg_replace_malloc.c:207)
==20494== by 0x460E49D: Java_com_ibm_jtc_demos_LeakyJNIApp_nativeMethod
(in /home/andhall/LeakyJNIApp/libleakyjniapp.so)
==20494== by 0x535CF56: ???
==20494== by 0x46423CB: gpProtectedRunCallInMethod
(in /usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so)
==20494== by 0x46441CF: signalProtectAndRunGlue
(in /usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so)
==20494== by 0x467E0D1: j9sig_protect
(in /usr/local/ibm-java2-i386-50/jre/bin/libj9prt23.so)
==20494== by 0x46425FD: gpProtectAndRun
(in /usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so)
==20494== by 0x4642A33: gpCheckCallin
(in /usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so)
==20494== by 0x464184C: callStaticVoidMethod
(in /usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so)
==20494== by 0x80499D3: main
(in /usr/local/ibm-java2-i386-50/jre/bin/java)
==20494==
==20494==
==20494== 65,536 (63,488 direct, 2,048 indirect) bytes in 62 blocks are definitely
lost in loss record 42 of 45
==20494== at 0x4024AB8: malloc (vg_replace_malloc.c:207)
==20494== by 0x460E49D: Java_com_ibm_jtc_demos_LeakyJNIApp_nativeMethod
(in /home/andhall/LeakyJNIApp/libleakyjniapp.so)
==20494== by 0x535CF56: ???
==20494== by 0x46423CB: gpProtectedRunCallInMethod
(in /usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so)
==20494== by 0x46441CF: signalProtectAndRunGlue
(in /usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so)
==20494== by 0x467E0D1: j9sig_protect
(in /usr/local/ibm-java2-i386-50/jre/bin/libj9prt23.so)
==20494== by 0x46425FD: gpProtectAndRun
(in /usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so)
==20494== by 0x4642A33: gpCheckCallin
(in /usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so)
==20494== by 0x464184C: callStaticVoidMethod
(in /usr/local/ibm-java2-i386-50/jre/bin/libj9vm23.so)
==20494== by 0x80499D3: main
(in /usr/local/ibm-java2-i386-50/jre/bin/java)
==20494==
==20494== LEAK SUMMARY:
==20494== definitely lost: 63,957 bytes in 69 blocks.
==20494== indirectly lost: 2,168 bytes in 12 blocks.
==20494== possibly lost: 8,600 bytes in 11 blocks.
==20494== still reachable: 5,156,340 bytes in 980 blocks.
==20494== suppressed: 0 bytes in 0 blocks.
==20494== Reachable blocks (those to which a pointer was found) are not shown.
==20494== To see them, rerun with: --leak-check=full --show-reachable=yes
堆栈的第二行显示内存是由 com.ibm.jtc.demos.LeakyJNIApp.nativeMethod() 方法泄漏的。
也可以使用一些专用调试应用程序来调试本机内存泄漏。随着时间的推移,会有更多工具(包括开源和专用的)被开发出来,这对于研究当前技术的发展现状很有帮助。
就目前而言,使用免费工具调试 Linux 上的本机内存泄漏比在 Windows 上完成相同的事情更具挑战性。UMDH 支持就地 调试 Windows 上本机内存泄漏,在 Linux 上,您可能需要进行一些传统的调试,而不是依赖工具来解决问题。下面是一些建议的调试步骤:
IBM Monitoring and Diagnostic Tools for Java:访问 IBM Java 工具页面。
IBM Support Assistant (ISA):这个免费支持框架包含 Garbage Collection and Memory Visualizer 和 IBM Guided Activity Assistant 等工具,可用于调试本机内存耗尽情况。
讨论
参与 developerWorks blogs 并加入developerWorks 社区。
关于作者
Andrew Hall 于 2004 年加入 IBM Java Technology Centre,他在 Java System Test 小组工作了两年。然后在 Java 服务团队工作了 18 个月,其间,他在多个平台上调试了数十个本机内存问题。他目前是 Java Reliability, Availability and Serviceability 团队的成员。在业余生活中,他喜欢阅读、摄影和玩魔术。
关闭 [x]
关于报告滥用的帮助 报告滥用
谢谢! 此内容已经标识给管理员注意。
关闭 [x]
关于报告滥用的帮助 报告滥用
报告滥用提交失败。 请稍后重试。
关闭 [x]
developerWorks:登录
如果您还没有注册到 IBM 注册系统,我们为给您带来的不便表示道歉,并请您马上注册。
现在注册。
IBM ID:
忘记 IBM ID?
密码:
忘记密码?
更改您的密码
登录之后:
留在当前页面My developerWorks 概要信息My developerWorks 首页
保持登录。