首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

理解 JVM 如何使用 Windows 和 Linux 上的本机内存(4)调试方法和技术

理解 JVM 如何使用 Windows 和 Linux 上的本机内存(4)调试方法和技术

当出现 java.lang.OutOfMemoryError 或看到有关内存不足的错误消息时,要做的第一件事是确定哪种类型的内存被耗尽。最简单的方式是首先检查 Java 堆是否被填满。如果 Java 堆未导致 OutOfMemory 条件,那么您应该分析本机堆使用情况。
检查 Java 堆检查堆使用情况的方法因 Java 实现不同而异。在 Java 5 和 6 的 IBM 实现上,当抛出 OutOfMemoryError 时会生成一个 javacore 文件来告诉您。javacore 文件通常在 Java 进程的工作目录中生成,以 javacore.日期.时间.pid.txt 的形式命名。如果您在文本编辑器中打开该文件,可以看到以下信息:
1
2
3
4
0SECTION       MEMINFO subcomponent dump routine
NULL           =================================
1STHEAPFREE    Bytes of Heap Space Free: 416760
1STHEAPALLOC   Bytes of Heap Space Allocated: 1344800




这部分信息显示在生成 javacore 时有多少空闲的 Java 堆。注意,显示的值为十六进制格式。如果因为分配条件不满足而抛出了 OutOfMemoryError 异常,则 GC 轨迹部分会显示如下信息:
1
2
3
1STGCHTYPE     GC History  
3STHSTTYPE     09:59:01:632262775 GMT j9mm.80 -   J9AllocateObject() returning NULL!
32 bytes requested for object of class 00147F80




J9AllocateObject() returning NULL! 意味着 Java 堆分配例程未成功完成,并且将抛出 OutOfMemoryError。
也可能由于垃圾收集器运行太频繁(意味着堆被填满了并且 Java 应用程序的运行速度将很慢或停止运行)而抛出 OutOfMemoryError。在这种情况下,您可能想要 Heap Space Free 值非常小,GC 轨迹将显示以下消息之一:
1
2
3
1STGCHTYPE     GC History  
3STHSTTYPE     09:59:01:632262775 GMT j9mm.83 -     Forcing J9AllocateObject()
to fail due to excessive GC




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




当 Sun 实现耗尽 Java 堆内存时,它使用异常消息来显示它耗尽的是 Java 堆:
1
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 本机内存分析器通过根据时间戳隔行扫描的方式,读取 Linux ps 命令的输出。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 版本支持新的命令行参数语法,那么得到的输出将类似于:
1
2
3
PID    VSZ   RSS
5826   3772  1960
5675   2492   760




如果您的 ps 版本不支持新语法,得到的输出将类似于:
1
2
3
PID VSZ,rss=RSS
5826        3772
5674        2488




如果您在一个较老的 ps 版本上运行,可以修改本机内存脚本,将      
1
ps -p $PID -o pid,vsz=VSZ,rss=RSS




行替换为
1
ps -p $PID -o pid,vsz,rss




将帮助面板中的脚本复制到一个文件中(在本例中名为 memscript.sh),找到您想要监控的 Java 进程的进程 ID (PID)(本例中为 1234)并运行:
1
./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.
  • 单击接近工具面板底部的 Launch 按钮。
  • 单击 Browse 按钮并找到日志文件。单击 OK 启动 GCMV。
一旦您拥有了本机内存随时间的使用情况的配置文件,您需要确定是存在本机内存泄漏,还是在尝试在可用空间中做太多事情。即使对于运行良好的 Java 应用程序,其本机内存占用也不是从启动开始就一成不变的。一些 Java 运行时系统(尤其是 JIT 编译器和类加载器)会不断初始化,这会消耗本机内存。初始化增加的内存将高居不下,但是如果初始本机内存占用接近于地址空间的限制,那么仅这个前期阶段就足以导致本机内存耗尽。图 6 给出了一个 Java 压力测试示例中的 GCMV 本机内存使用情况,其中突出显示了前期阶段。
图 6. GCMV 的 Linux 本机内存使用示例,其中显示了前期阶段本机内存占用也可能应工作负载不同而异。如果您的应用程序创建了较多进程来处理传入的工作负载,或者根据应用于系统的负载量按比例分配本机存储(比如直接 ByteBuffer),则可能由于负载过高而耗尽本机内存。
由于 JVM 前期阶段的本机内存增长而耗尽本机内存,以及内存使用随负载增加而增加,这些都是尝试在可用空间中做太多事情的例子。在这些场景中,您的选择是:
  • 减少本机内存使用。缩小 Java 堆大小是一个好的开端。
  • 限制本机内存使用。如果您的本机内存随负载增加而增加,可以采取某种方式限制负载或为负载分配的资源。
  • 增加可用地址空间。这可以通过以下方式实现:调优您的操作系统(例如,在 Windows 上使用 /3GB 开关增加用户空间,或者在 Linux 上使用庞大的内核空间),更换平台(Linux 通常拥有比 Windows 更多的用户空间),或者 。
一种实际的本机内存泄漏表现为本机堆的持续增长,这些内存不会在移除负载或运行垃圾收集器时减少。内存泄漏程度因负载不同而不同,但泄漏的总内存不会下降。泄漏的内存不可能被引用,因此它可以被交换出去,并保持被交换出去的状态。
当遇到内存泄漏时,您的选择很有限。您可以增加用户空间(这样就会有更多的空间供泄漏),但这仅能延缓最终的内存耗尽。如果您拥有足够的物理内存和地址空间,并且会在进程地址空间耗尽之前重启应用程序,那么可以允许地址空间继续泄漏。
返回列表