jvm内存详解(二)_EndlessWait_百度空间

为了帮助您了解本机内存耗尽如何影响您正使用的 Java 实现,本文的示例代码(参见 )中包含了一些 Java 程序,用于以不同方式触发本机堆耗尽。这些示例使用通过 C 语言编写的本机库来消耗所有本机地址空间,然后尝试执行一些使用本机内存的操作。提供的示例已经过编译,编译它们的指令包含在示例包的{dj0}目录下的 README.html 文件中。

com.ibm.jtc.demos.NativeMemoryGlutton 类提供了 gobbleMemory() 方法,它在一个循环中调用 malloc,直到几乎所有本机内存都已耗尽。完成任务之后,它通过以下方式输出分配给标准错误的字节数:


针对在 32 位 Windows 上运行的 Sun 和 IBM Java 运行时的每次演示,其输出都已被捕获。提供的二进制文件已在以下操作系统上进行了测试:

  • Linux x86
  • Linux PPC 32
  • Linux 390 31
  • Windows x86

使用以下 Sun Java 运行时版本捕获输出:


使用的 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() 来尝试为一个新的操作系统线程分配内存。此尝试会失败并抛出 OutOfMemoryErrorJVMDUMP 行通知用户 Java 运行时已经生成了标准的 OutOfMemoryError 调试数据。

尝试处理{dy}个 OutOfMemoryError 会导致第二个错误 —— :OutOfMemoryError, ENOMEM error in ZipFile.open。当本机进程内存耗尽时通常会抛出多个 OutOfMemoryErrorFailed 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)

尽管堆栈轨迹和错误消息稍有不同,但其行为在本质上是一样的:本机分配失败并抛出 java.lang.OutOfMemoryError。此场景中抛出的 OutOfMemoryError 与由于 Java 堆耗尽而抛出的错误的惟一区别在于消息。

com.ibm.jtc.demos.DirectByteBufferUnderNativeStarvation 类尝试在地址空间耗尽时分配一个直接(也就是受本机支持的)java.nio.ByteBuffer 对象。当在 IBM Java 运行时上运行时,它生成以下输出:

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)




当出现 java.lang.OutOfMemoryError 或看到有关内存不足的错误消息时,要做的{dy}件事是确定哪种类型的内存被耗尽。最简单的方式是首先检查 Java 堆是否被填满。如果 Java 堆未导致 OutOfMemory 条件,那么您应该分析本机堆使用情况。

检查堆使用情况的方法因 Java 实现不同而异。在 Java 5 和 6 的 IBM 实现上,当抛出 OutOfMemoryError 时会生成一个 javacore 文件来告诉您。javacore 文件通常在 Java 进程的工作目录中生成,以 javacore.日期.时间.pid.txt 的形式命名。如果您在文本编辑器中打开该文件,可以看到以下信息:


这部分信息显示在生成 javacore 时有多少空闲的 Java 堆。注意,显示的值为十六进制格式。如果因为分配条件不满足而抛出了 OutOfMemoryError 异常,则 GC 轨迹部分会显示如下信息:


J9AllocateObject() returning NULL! 意味着 Java 堆分配例程未成功完成,并且将抛出 OutOfMemoryError

也可能由于垃圾收集器运行太频繁(意味着堆被填满了并且 Java 应用程序的运行速度将很慢或停止运行)而抛出 OutOfMemoryError。在这种情况下,您可能想要 Heap Space Free 值非常小,GC 轨迹将显示以下消息之一:



当 Sun 实现耗尽 Java 堆内存时,它使用异常消息来显示它耗尽的是 Java 堆:


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 的工具,但是它提供了几个替代工具。命令行工具(比如 pstoppmap)能够显示应用程序的本机内存占用情况。尽管获取进程内存使用情况的实时快照非常有用,但通过记录内存随时间的使用情况,您能够更好地理解本机内存是如何被使用的。为此,能够采取的一种方式是使用 GCMV。

GCMV 最初编写用于分析冗长的 GC 日志,允许用户在调优垃圾收集器时查看 Java 堆使用情况和 GC 性能的变化。GCMV 后来进行了扩展,支持分析其他数据源,包括 Linux 和 AIX 本机内存数据。GCMV 是作为 IBM Support Assistant (ISA) 的插件发布的。

要使用 GCMV 分析 Linux 本机内存配置文件,您首先必须使用脚本收集本机内存数据。GCMV 的 Linux 本机内存分析器通过根据时间戳隔行扫描的方式,读取 Linux ps 命令的输出。GCMV 提供了一个脚本来帮助以正确形式记录收集数据。要找到该脚本:

  1. 下载并安装 Version 4(或更高版本),然后安装 GCMV 工具插件。
  2. 启动 ISA。
  3. 从菜单栏单击 Help >> Help Contents,打开 ISA 帮助菜单。
  4. 在左侧窗格的 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 插件。



IBM Support Assistant 帮助文件

GCMV 帮助文件中提供的脚本使用的 ps 命令仅适用于{zx1}的 ps 版本。在一些旧的 Linux 分发版中,帮助文件中的命令将会生成错误信息。要查看您的 Linux 分发版上的行为,可以尝试运行 ps -o pid,vsz=VSZ,rss=RSS。如果您的 ps 版本支持新的命令行参数语法,那么得到的输出将类似于:


如果您的 ps 版本不支持新语法,得到的输出将类似于:


如果您在一个较老的 ps 版本上运行,可以修改本机内存脚本,将
行替换为

将帮助面板中的脚本复制到一个文件中(在本例中名为 memscript.sh),找到您想要监控的 Java 进程的进程 ID (PID)(本例中为 1234)并运行:


这会把本机内存日志写入到 ps.out 中。要分析内存使用情况:

  1. 在 ISA 中,从 Launch Activity 下拉菜单选择 Analyze Problem
  2. 选择接近 Analyze Problem 面板顶部的 Tools 标签。
  3. 选择 IBM Monitoring and Diagnostic Tools for Java - Garbage Collection and Memory Visualizer.
  4. 单击接近工具面板底部的 Launch 按钮。
  5. 单击 Browse 按钮并找到日志文件。单击 OK 启动 GCMV。

一旦您拥有了本机内存随时间的使用情况的配置文件,您需要确定是存在本机内存泄漏,还是在尝试在可用空间中做太多事情。即使对于运行良好的 Java 应用程序,其本机内存占用也不是从启动开始就一成不变的。一些 Java 运行时系统(尤其是 JIT 编译器和类加载器)会不断初始化,这会消耗本机内存。初始化增加的内存将高居不下,但是如果初始本机内存占用接近于地址空间的限制,那么仅这个前期阶段就足以导致本机内存耗尽。图 6 给出了一个 Java 压力测试示例中的 GCMV 本机内存使用情况,其中突出显示了前期阶段。



GCMV 本机内存使用

本机内存占用也可能应工作负载不同而异。如果您的应用程序创建了较多进程来处理传入的工作负载,或者根据应用于系统的负载量按比例分配本机存储(比如直接 ByteBuffer),则可能由于负载过高而耗尽本机内存。

由于 JVM 前期阶段的本机内存增长而耗尽本机内存,以及内存使用随负载增加而增加,这些都是尝试在可用空间中做太多事情的例子。在这些场景中,您的选择是:

  • 减少本机内存使用。缩小 Java 堆大小是一个好的开端。
  • 限制本机内存使用。如果您的本机内存随负载增加而增加,可以采取某种方式限制负载或为负载分配的资源。
  • 增加可用地址空间。这可以通过以下方式实现:调优您的操作系统(例如,在 Windows 上使用 /3GB 开关增加用户空间,或者在 Linux 上使用庞大的内核空间),更换平台(Linux 通常拥有比 Windows 更多的用户空间),或者 。

一种实际的本机内存泄漏表现为本机堆的持续增长,这些内存不会在移除负载或运行垃圾收集器时减少。内存泄漏程度因负载不同而不同,但泄漏的总内存不会下降。泄漏的内存不可能被引用,因此它可以被交换出去,并保持被交换出去的状态。

当遇到内存泄漏时,您的选择很有限。您可以增加用户空间(这样就会有更多的空间供泄漏),但这仅能延缓最终的内存耗尽。如果您拥有足够的物理内存和地址空间,并且会在进程地址空间耗尽之前重启应用程序,那么可以允许地址空间继续泄漏。


引用:http://www.ibm.com/developerworks/cn/java/j-nativememory-linux/index.html



郑重声明:资讯 【jvm内存详解(二)_EndlessWait_百度空间】由 发布,版权归原作者及其所在单位,其原创性以及文中陈述文字和内容未经(企业库qiyeku.com)证实,请读者仅作参考,并请自行核实相关内容。若本文有侵犯到您的版权, 请你提供相关证明及申请并与我们联系(qiyeku # qq.com)或【在线投诉】,我们审核后将会尽快处理。
—— 相关资讯 ——