[转帖]内存详解2_Tomcat, WebLogic及J2EE讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  Tomcat, WebLogic及J2EE讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 3398 | 回复: 0   主题: [转帖]内存详解2        下一篇 
songjian
注册用户
等级:上尉
经验:711
发帖:60
精华:0
注册:2012-11-8
状态:离线
发送短消息息给songjian 加好友    发送短消息息给songjian 发消息
发表于: IP:您无权察看 2012-11-9 10:25:06 | [全部帖] [楼主帖] 楼主

如何知道本机内存已耗尽?
Java  运行时处理 Java 堆耗尽与处理本机堆耗尽的方式不同,但这两种情况都呈现类似的症状。Java 应用程序发现 Java 堆耗尽时很难正常运行 — 因为 Java 应用程序若不分配对象则很难执行任何任务。较差的 GC 性能和 OutOfMemoryError 表明  Java 堆已填满。
相反,当  Java 运行时启动并且应用程序稳定运行之后,即便本机堆完全耗尽也能继续正常运行。这并不会出示任何古怪的行为,因为需要本机内存的操作比需要 Java 堆分配的操作少很多。虽然需要本机内存的操作因 JVM 实现而异,但也有一些常见的例子:启动线程、加载类以及执行特定类别的网络和文件 I/O
本机内存不足行为的一致性也比  Java 堆内存不足行为差,因为没有针对本机堆分配的单点控制。但是,所有的 Java 堆分配都在 Java 内存管理系统的控制之下,任何本机代码 — 无论在 JVMJava 类库还是应用程序代码的内部 — 都可以执行本机内存分配,并造成它出错。然后,尝试进行分配的代码可以按设计人员的方式来处理它:它可以通过 JNI 接口来抛出一个 OutOfMemoryError,在屏幕上打印输出消息,出现故障并在稍后再次尝试,或执行其他任务。
缺乏可预测行为意味着无法通过一种简单的方式来识别本机内存耗尽。相反,您需要使用来自  OS 和 Java 运行时的数据来确认诊断。

本机内存耗尽示例
为了帮助您了解本机内存耗尽对 Java 运行时的影响,本文的示例代码(参见 下载)包含一些通过不同方式触发本机堆耗尽的 Java 程序。这些示例使用通过 编写的本机库占用所有本机进程空间,然后尝试执行一些使用本机内存的操作。提供的示例已经经过生成,但编译它们的指令包含在示例包的顶级目录的 README.html 文件中。
com.ibm.jtc.demos.NativeMemoryGlutton类提供 gobbleMemory() 方法,该方法在循环中调用 malloc,直到几乎所有本机内存耗尽。当它完成其任务时,它会打印输出分配给标准错误的字节数,如下所示:

perl get_memory_use.pl javacore.20080111.081905.1311.txtSegment Usage           Reserved BytesClass Memory            281767824Internal Memory         25763872JIT Code Cache          67108864JIT Data Cache          33554432Object Memory           536870912


每个示例的输出都在 32 位 AIX 上运行的 IBM Java 运行时环境中被捕获。示例程序的二进制文件提供在示例包中(参见 下载)。
所使用的 IBM Java 运行时版本如下:

尝试在本机内存耗时启动线程
com.ibm.jtc.demos.StartingAThreadUnderNativeStarvation类尝试在进程地址空间耗尽时启动线程。这是发现 Java 进程内存不足的常用方法,因为许多应用程序都会在它们的生命周期过程中启动线程。
StartingAThreadUnderNativeStarvation的输出如下:

Allocated 1953546736 bytes of native memory before running out


调用 java.lang.Thread.start() 尝试为新 OS 线程分配内存。尝试失败并造成抛出一个 OutOfMemoryError。JVMDUMP 行通知用户 Java 运行时已经生成了它的标准 OutOfMemoryError 调试数据。
尝试处理第一个 OutOfMemoryError 会造成第二个错误 — :OutOfMemoryError, ENOMEM error in ZipFile.open。当本机内存耗尽时,经常会出现多个 OutOfMemoryError,因为一些默认 OutOfMemoryError 处理例程可能需要分配本机内存。这听起来没有什么作用,但 Java 应用程序抛出的大多数 OutOfMemoryError 都是由 Java 堆内存不足造成的,这不会阻止运行时分配本机存储。区分本场景中的 OutOfMemoryError 抛出与 Java 堆耗尽造成的其他抛出的惟一方法就是消息。
尝试在本机内存不足时分配直接 ByteBuffer
com.ibm.jtc.demos.DirectByteBufferUnderNativeStarvation类尝试在地址空间耗尽时分配一个直接(也就是,本机支持的)java.nio.ByteBuffer 对象。它生成以下输出:

java version "1.5.0"Java(TM) 2 Runtime Environment, Standard Edition (build pap32devifx-20080811c (SR8a))IBM J9 VM (build 2.3, J2RE 1.5.0 IBM J9 2.3 AIX ppc-32 j9vmap3223ifx-20080811 (JIT enabled)J9VM - 20080809_21892_bHdSMrJIT  - 20080620_1845_r8GC   - 200806_19)JCL  - 20080811b


在这个场景中,您可以看到 OutOfMemoryError 抛出造成的许多 JVMDUMP 消息。Java 跟踪引擎生成的一些 UTE 错误消息显示它不能分配本机缓冲。这些 UTE 错误消息是本机内存不足的一般症状,因为跟踪引擎在默认情况是启动且活动的。最后,打印输出两个 OutOfMemoryError — zip 库中分配第二次失败,以及 java.nio.DirectByteBuffer 中的原始错误。

调试方法和技巧

$ ./run_thread_demo_linux_aix_32.shAllocated 2652372992 bytes of native memory before running outJVMDUMP006I Processing Dump Event "systhrow", detail "java/lang/OutOfMemoryError" - Please Wait.JVMDUMP007I JVM Requesting Snap Dump using '/u2/andhall/aix_samples_pack/OutOfNativeBehaviour/Snap.20081207.105130.487430.0001.trc'JVMDUMP010I Snap Dump written to /u2/andhall/aix_samples_pack/OutOfNativeBehaviour/Snap.20081207.105130.487430.0001.trcJVMDUMP007I JVM Requesting Heap Dump using '/u2/andhall/aix_samples_pack/OutOfNativeBehaviour/heapdump.20081207.105130.487430.0002.phd'JVMDUMP010I Heap Dump written to /u2/andhall/aix_samples_pack/OutOfNativeBehaviour/heapdump.20081207.105130.487430.0002.phdJVMDUMP007I JVM Requesting Java Dump using '/u2/andhall/aix_samples_pack/OutOfNativeBehaviour/javacore.20081207.105130.487430.0003.txt'JVMDUMP010I Java Dump written to /u2/andhall/aix_samples_pack/OutOfNativeBehaviour/javacore.20081207.105130.487430.0003.txtJVMDUMP013I 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:1111)        at java.security.AccessController.doPrivileged(AccessController.java:197)        at java.util.ResourceBundle.loadBundle(ResourceBundle.java:1107)        at java.util.ResourceBundle.findBundle(ResourceBundle.java:952)        at java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:789)        at java.util.ResourceBundle.getBundle(ResourceBundle.java:726)        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 create a thread: retVal -1073741830, errno 11        at java.lang.Thread.startImpl(Native Method)        at java.lang.Thread.start(Thread.java:979)        at com.ibm.jtc.demos.StartingAThreadUnderNativeStarvation.main(StartingAThreadUnderNativeStarvation.java:33)


遇到 java.lang.OutOfMemoryError 或与内存不足相关的错误消息时的第一件事是确定耗尽的是哪种内存。最简单的方法是先检查 Java 堆是否是满的。如果 Java 堆未造成 OutOfMemory 条件,则您应该分配本机堆的使用。
检查 Java 
要检查 Java 堆使用,您可以查看抛出 OutOfMemoryError 时生成的 javacore 文件,或使用详细的 GC 数据。javacore 文件通常在 Java 进程的工作目录中生成,并且采用 javacore.<date>.<time>.<pid>.txt 形式的名称。如果您在文本编辑器中打开文件,可以看到如下所示的内容:

本节讨论生成 javacore 时 Java 堆有空闲情况。注意它采用十六进制的值。如果由于无法满足堆分配而抛出 OutOfMemoryError,则 GC 跟踪部分将显示如下内容:

$ ./run_directbytebuffer_demo_aix_32.shAllocated 2652372992 bytes of native memory before running outJVMDUMP006I Processing Dump Event "systhrow", detail "java/lang/OutOfMemoryError" - Please Wait.JVMDUMP007I JVM Requesting Snap Dump using '/u2/andhall/aix_samples_pack/OutOfNativeBehaviour/Snap.20081207.105307.610498.0001.trc'JVMDUMP010I Snap Dump written to /u2/andhall/aix_samples_pack/OutOfNativeBehaviour/Snap.20081207.105307.610498.0001.trcJVMDUMP007I JVM Requesting Heap Dump using '/u2/andhall/aix_samples_pack/OutOfNativeBehaviour/heapdump.20081207.105307.610498.0002.phd'JVMDUMP010I Heap Dump written to /u2/andhall/aix_samples_pack/OutOfNativeBehaviour/heapdump.20081207.105307.610498.0002.phdJVMDUMP007I JVM Requesting Java Dump using '/u2/andhall/aix_samples_pack/OutOfNativeBehaviour/javacore.20081207.105307.610498.0003.txt'JVMDUMP010I Java Dump written to /u2/andhall/aix_samples_pack/OutOfNativeBehaviour/javacore.20081207.105307.610498.0003.txtJVMDUMP013I Processed Dump Event "systhrow", detail "java/lang/OutOfMemoryError".JVMDUMP006I Processing Dump Event "systhrow", detail "java/lang/OutOfMemoryError" - Please Wait.JVMDUMP007I JVM Requesting Snap Dump using '/u2/andhall/aix_samples_pack/OutOfNativeBehaviour/Snap.20081207.105308.610498.0004.trc'JVMDUMP010I Snap Dump written to /u2/andhall/aix_samples_pack/OutOfNativeBehaviour/Snap.20081207.105308.610498.0004.trcJVMDUMP007I JVM Requesting Heap Dump using '/u2/andhall/aix_samples_pack/OutOfNativeBehaviour/heapdump.20081207.105308.610498.0005.phd'JVMDUMP010I Heap Dump written to /u2/andhall/aix_samples_pack/OutOfNativeBehaviour/heapdump.20081207.105308.610498.0005.phdJVMDUMP007I JVM Requesting Java Dump using '/u2/andhall/aix_samples_pack/OutOfNativeBehaviour/javacore.20081207.105308.610498.0006.txt'UTE430: can't allocate bufferUTE437: Unable to load formatStrings for j9mmUTE430: can't allocate bufferUTE437: Unable to load formatStrings for j9mmUTE430: can't allocate bufferUTE437: Unable to load formatStrings for j9mmUTE430: can't allocate bufferUTE437: Unable to load formatStrings for j9mmUTE430: can't allocate bufferUTE437: Unable to load formatStrings for j9mmJVMDUMP013I 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:1111)        at java.security.AccessController.doPrivileged(AccessController.java:197)        at java.util.ResourceBundle.loadBundle(ResourceBundle.java:1107)        at java.util.ResourceBundle.findBundle(ResourceBundle.java:952)        at java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:789)        at java.util.ResourceBundle.getBundle(ResourceBundle.java:726)        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: Unable to allocate 1048576 bytes of direct memory after 5 retries        at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:197)        at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:303)        at com.ibm.jtc.demos.DirectByteBufferUnderNativeStarvation.main(DirectByteBufferUnderNativeStarvation.java:27)Caused by: java.lang.OutOfMemoryError        at sun.misc.Unsafe.allocateMemory(Native Method)        at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:184)        ... 2 more


J9AllocateObject() returning NULL!表示对象分配例程已成功完成,并且将抛出一个 OutOfMemoryError。
还有可能因为垃圾收集器运行过于频繁而抛出一个 OutOfMemoryError(表示堆已填满,并且 Java 应用程序几乎没有进展)。在这种情况下,您会希望 Heap Space Free 值非常小,并且 GC 历史将显示下面一条消息:

北京联动北方科技有限公司

针对 IBM 产品的调试
本文的指导方针是适用于本机内存不足场景的一般调试原则,它们将帮助您调试自己的本机内存问题。如果您要向 IBM 发起问题请求,请始终按照产品的 MustGather 文档收集支持团队所需格式的数据(参见 参考资料)。IBM Guided Activity Assistant — 集成在 IBM Support Assistant (ISA) 工作台中的一款工具 — 提供了调试若干 IBM 产品问题的最新工作流,这些产品包括 Developer Kit for Java

-verbose:gc命令行选项生成包含 GC 统计数据(包括堆占用情况)的跟踪数据。IBM Monitoring and Diagnostic Tools for Java - Garbage Collection and Memory Visualizer (GCMV) 工具可以利用此信息显示 Java 堆是否在增加。参见 参考资料,了解如何收集和利用 verbose:gc 数据。
测量本机堆使用
如果您已经确定内存不足情况不是由 Java 堆耗尽造成的,则下一个阶段是分配您的本机内存使用。
如果您熟悉 AIX 进程调优,则可以使用喜好的工作链来监控本机进程大小。一种选择是使用 IBM Monitoring and Diagnostic Tools for Java - Garbage Collection and Memory Visualizer (GCMV) 工具。
GCMV 最初用于分析详细 GC 日志,这允许用户在调优垃圾收集器时查看 Java 堆使用和性能的变化。GCMV 随后得到了扩展,支持分析其他数据源,包括 Linux 和 AIX 本机内存日志。GCMV 作为 ISA 插件随带提供。参见 参考资料,了解如何下载和安装 ISA 及 GCMV,以及如何使用 GCMV 调试 GC 性能问题。
要使用 GCMV 分析 AIX 本机内存配置文件,您必须先使用一个脚本来收集本机内存数据。GCMV 的 AIX 本机内存分析器从 AIX svmon 命令读取输出。GCMV 帮助文档中提供了一个脚本,用于收集正确格式的数据。要找到该脚本:

· 下载并安装 ISA Version 4(或以上),然后安装 GCMV 工具插件(参见 参考资料 了解详细信息)。

· 启动 ISA

· 调出 ISA 帮助菜单,方法是单击菜单栏中的 Help >> Help Contents。

· 在 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 >> AIX Native Memory 下方的右侧窗格中找到 AIX 本机内存指令。

图 展示了 GCMV 帮助树中包含监控脚本的区域。如果帮助文件在支持数据类型选项卡中没有本机内存区域,那么您需要升级到最新的 GCMV 包。

图 5. GCMV AIX 内存监控脚本在 ISA 帮助对话框中的位置
北京联动北方科技有限公司
要使用脚本,将它移动到您的 AIX 机器上,然后启动要监控的 Java 进程。使用 ps 获取 Java 进程的进程标识符(PID),然后启动监控脚本(其中,pid 是要监控的进程的 IDoutput_file 是存储内存日志的文件 — GCMV 将要分析的文件):

要分析内存日志:

· 在 ISA 中,从 Launch Activity 下拉菜单中选择 Analyze Problem。

· 在 Analyze Problem 面板的顶部选择 Tools 选项卡。

· 选择 IBM Monitoring and Diagnostic Tools for Java - Garbage Collection and Memory Visualizer。

· 单击工具面板底部的 Launch 按钮。

· 单击 Browse 按钮并找到日志文件,单击 OK 启动 GCMV

分析本机内存使用一段时间之后,您需要确定是否出现了本机内存泄漏,还是可用空间的工作太多。即便对于性能良好的 Java 应用程序,本机内存占用在启动之后也不是恒定的。一些 Java 运行时系统 — 特别是 JIT 编译器和类加载程序 — 的初始化过程需要一段时间,这也会消耗本机内存。初始后的内存增长速度会逐渐增加,但如果在您的场景中,初始内存占用连接地址空间的限制,则这个热身阶段足以造成内存不足。
图 显示了一项 Java 压力测试的本机内存占用情况。灰色加亮区显示,在热身阶段中,本机内存占用增加,然后随进程进入稳定状态而趋于平稳。

图 6. AIX 本机内存分析显示热身阶段
北京联动北方科技有限公司
还可以让本机内存占用与工作负载相关联。如果您的应用程序创建更多线程来处理传入工作负载,或按比例给应用于系统的负载分配本机支持存储(比如直接 ByteBuffer),那么您可能在高负载下耗尽本机内存。
JVM 热身阶段本机内存的增长以及负载的按比例增长造成的本机内存耗尽是尝试在可用空间内完成过多工作的例子。在这些场景中,您的选择如下:

· 减少本机内存使用。减少 Java 堆大小是一个很好的选择。

· 限制本机内存使用。如果您的本机内存增长随负载而变化,则应该通过某种方式限制负载或因此而分配的资源。

· 增加可用的地址空间量。您可以设置 LDR_CNTRL 环境变量来指定一个不同的内存模型配置,或者考虑 迁移到 64 位。

真正的本机内存泄漏表现为本机堆持续增加,当负载移除或垃圾收集器运行时仍然不降低。内存泄漏率因负载而异,但泄漏的总内存则不会降低。泄漏的内存不太可能被引用,因此可以将它们换出并维持此状态。
在面对泄漏时,您的选择将受到限制。可以通过 LDR_CNTRL 环境变量增加地址空间的总量(因此可泄漏的空间变得更大),但这只能延缓内存耗尽的时间。如果您有足够的物理内存和地址空间,您可以允许泄漏继续,但必须在进程地址空间耗尽之前重新启动应用程序。
谁在使用我的本机内存?
当您确定本机内存耗尽时,下一个逻辑问题是:谁在使用这些内存?AIX 未存储关于哪些代码路径默认分配了特定内存块的信息,但这些信息很容易获得。
在尝试理解本机内存的去处时,第一个步骤是算出您的 Java 设置大约需要多少本机内存。您可以根据以下指导方针大致估算下限:

· Java 堆占用 -Xmx 值。

· 每个 Java 线程都有一个本机栈和一个 Java 栈。在 AIX 上,每个线程至少需要使用 256KB

· 直接 ByteBuffer 至少占用提供给 allocate() 例程的值。

如果您的总空间小于最大用户空间,那么您的安全性是不确定的。Java 运行时中的许多其他组件都可能会分配足够的内存,从而引起问题;但是,如果您的初始计算表明已经接近最大用户空间,则您很有可能遇到内存问题。如果您怀疑存在本机内存泄漏,或者希望理解内存的准确去向,可以使用一些有用的工具。
AIX 上的许多可用的内存调试器都属于以下类别:

· 预处理器级别。这些工具需要在测试中通过源编译一个头部。可以使用其中一款工具重新编译您自己的 JNI 库,以跟踪代码中的本机内存泄漏。Dmalloc 是这种工具的一个例子(参见 参考资料)。

· 链接器级别。这些工具要求测试中的二进制文件与测试中的库重新链接到一起。这对于单独的 JNI 库可行,但不建议用于整个 Java 运行时,因为可能不支持使用经过修改的二进制文件进行运行。Ccmalloc 是这种工具的一个例子(参见 参考资料)。

· 运行时链接器级别。这些工具使用 LD_PRELOAD 环境变量预加载库,用于将标准内存例程替换为插装版本。它们不要求重新编译或者重新链接源代码,但其中许多都不能与 Java 运行时很好地兼容(Linux 等其他操作上可用的工具,比如 NJAMD,也不支持 AIX)。

· 操作系统级别。AIX 提供了 MALLOCDEBUG 工具,用于调试本机内存泄漏。

我建议您阅读 在 AIX V5.3 中使用 MALLOCDEBUG 隔离并解决内存泄漏” 这篇文章,了解如何使用 MALLOCDEBUG 诊断内存泄漏。在这里,我将专注于泄漏 Java 应用程序的输出。您将了解如何使用 MALLOCDEBUG 调试遇到本机内存泄漏的 JNI 应用程序。
本文的示例包(参见 下载)包含一个名称为 LeakyJNIApp 的 Java 应用程序;它在循环中调用一个会泄漏本机内存的 JNI 方法。默认情况下,它会一直运行直到本机内存耗尽;要让它停止,将运行时间(以秒为单位)作为命令行参数传递给它。
为 malloc 调试配置环境,方法是设置 MALLOCDEBUG 和 MALLOCTYPE environment variables:

0SECTION       MEMINFO subcomponent dump routineNULL           =================================1STHEAPFREE    Bytes of Heap Space Free: 416760 1STHEAPALLOC   Bytes of Heap Space Allocated: 1344800


添加 stack_depth:3 参数,限制调用 malloc 时收集的栈跟踪。JVM 拥有一个惟一的线程栈结构,它会使栈遍历应用程序出现混乱并造成崩溃。通过将栈深度限制为三层,您应该能避免意外行为。
配置好环境之后,运行 LeakyJNIApp 10 秒钟,并捕获 stderr 输出,其中包含 malloc 记录:

memory_log.txt 文件现在包含泄漏内存块的详细信息:

1STGCHTYPE     GC History  3STHSTTYPE     09:59:01:632262775 GMT j9mm.80 -   J9AllocateObject() returning NULL!32 bytes requested for object of class 00147F80


通过查看内存记录文件,您可以确定问题所在。或者,您可以使用 在 AIX V5.3 中使用 MALLOCDEBUG 隔离并解决内存泄漏” 提供的 format_mallocdebug_op.sh 脚本汇总内存记录。
对 memory_log.txt 文件运行汇总脚本会生成以下输出:

1STGCHTYPE     GC History  3STHSTTYPE     09:59:01:632262775 GMT j9mm.83 -     Forcing J9AllocateObject()to fail due to excessive GC


这显示了来自 LeakyJNIApp.nativeMethod() 的泄漏。
一些专用调试应用程序也提供了类似的功能。更多(开源和专用的)工具一直在开发之中,研究目前的最新技术是非常有必要的。
OS 和第三方工具可以让调试更加简单,但是它们消除了合理调试技巧的需求。一些建议的步骤如下:

· 提取测试用例。生成一个可以重新引起本机泄漏的独立环境。这可以让调试更加简单。

· 尽可以缩减测试用例。尝试删除一些功能以确定哪些代码路径在造成本机泄漏。如果您有自己的 JNI 库,尝试完全禁用它们,以确定它们是否会造成泄漏。

· 减小 Java 堆大小。Java 堆可能是进程中最大的虚拟地址空间使用者。通过减小 Java 堆,您为本机内存的其他使用者提供了更多可用的空间,这样可以让应用程序运行更长时间。

· 关联本机进程大小。了解本机内存在一段时间内的使用情况之后,您可以将它与应用程序负载以及 GC 数据进行比较。如果泄漏率与负载水平成正比,则表明泄漏是由各事务或操作上的某个因素造成的。如果本机进程大小在执行 GC 时显著降低,则表明您未遇到内存泄漏 — 而是本机支持造成的对象累积(比如直接 ByteBuffer)。您可以减少本机支持对象占用的内存量,方法是减少 Java 堆大小(因此强制收集更加频繁地执行)或自己在对象缓存中(而不是依靠垃圾收集来清理它们)管理它们。

如果您认为泄漏或内存增长是由 Java 运行时本身造成时,您会希望运行时供应商参与进一步调试。

消除限制:转换成 64 
在 32 位 Java 运行时中,很容易出现本机内存不足的情况,因为地址空间相对较小。32 位操作系统提供的 到 4GB 用户空间经常小于系统附带的物理内存总量,并且现代的数据密集型应用程序在扩展后能很容易地填满可用空间。
如果您的应用程序不能适应 32 位地址空间,那么您可以通过迁移到 64 位 Java 运行时来获取更多用户空间。如果您可以在 AIX 上运行 64 位 Java 运行时,则 448PB 的地址空间提供了非常大的 Java 堆,并能减少与地址空间相关的问题。
但是,迁移到 64 位并不是适用于所有本机内存问题的通用解决方案;您仍然需要足够的物理内存来保存所有数据。如果您的 Java 运行时不能完全加载到��理内存中,则性能会差到极点,因为操作系统需要与交换空间来回复制 Java 运行时数据。基于此原因,迁移到 64 位并不是针对内存泄漏的万能解决方案 — 您只是增加了可以泄漏的空间,这只是延长了应用程序正常运行的时间。
不能在 64 位运行时中使用 32 位本机代码;任何本机代码(JNI 库、JVM Tool Interface [JVMTI]JVM Profiling Interface [JVMPI] 和 JVM Debug Interface [JVMDI] 代理)都必须针对 64 位进行重新编译。在相同的硬件上,64 位运行时的性能有时还会比相应的 32 位运行时慢。64 位运行时使用 64 位指针(本机地址引用),因此 64 位运行时中的相同对象会占用更多的空间。更大的对象意味着用更大的堆来保存相同大小的数据,同时维持类似的 GC 性能,这会拖慢操作系统和硬件内存系统。令人惊讶的是,更大的 Java 堆并不一定意味着更长的 GC 暂停时间,因为暂停时间主要由堆中的活动数据决定(这些数据可能未增加),并且一些 GC 算法对于更大的堆效率更高。
虽然 64 位运行时的性能历来比相应的 32 位运行时低,但这种情况在 IBM Developer Kit for Java 6.0 中得到了显著改善。此外,压缩引用技术(通过 -Xcompressedrefs 命令行参数启用)允许您在执行 32 位对象寻址时使用大 Java 堆(在 Service Refresh 2 上最大可达到 20-30GB)。这样可以清除造成之前 64 位运行时运行过慢的 对象膨胀
Java 运行时的性能对比不在本文的讨论范围之内 — 但如果您考虑迁移到 64 位运行时,则有必要先在 64 位运行时中测试您的应用程序,并使用 IBM Developer Kit for Java 6 利用压缩引用的优势。

结束语
在设计和运行大型 Java 应用程序时,理解本机内存至关重要,但是这一点通常被忽略,因为它与复杂的硬件和操作系统细节密切相关,Java 运行时的目的正是帮助我们规避这些细节。JRE 是一个本机进程,它必须在由这些纷繁复杂的细节定义的环境中工作。要从 Java 应用程序中获得最佳的性能,您必须理解应用程序如何影响 Java 运行时的本机内存使用。
耗尽本机内存与耗尽 Java 堆很相似,但它需要不同的工具集来调试和解决。修复本机内存问题的关键在于理解运行您的 Java 应用程序的硬件和操作系统施加的限制,并将其与操作系统工具知识结合起来,监控本机内存使用。通过这种方法,您将能够解决 Java 应用程序产生的一些非常棘手的问题。




赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论