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

理解 JVM 如何使用 Windows 和 Linux 上的本机内存(3)本机内存耗尽会发生什么?

理解 JVM 如何使用 Windows 和 Linux 上的本机内存(3)本机内存耗尽会发生什么?

Java 运行时善于以不同的方式来处理 Java 堆的耗尽与本机堆的耗尽,但这两种情形具有类似的症状。当 Java 堆耗尽时,Java 应用程序很难正常运行,因为 Java 应用程序必须通过分配对象来完成工作。只要 Java 堆被填满,就会出现糟糕的 GC 性能并抛出表示 Java 堆被填满的 OutOfMemoryError。
相反,一旦 Java 运行时开始运行并且应用程序处于稳定状态,它可以在本机堆完全耗尽之后继续正常运行。不一定会发生奇怪的行为,因为需要分配本机内存的操作比需要分配 Java 堆的操作少得多。尽管需要本机内存的操作因 JVM 实现不同而异,但也有一些操作很常见:启动线程、加载类以及执行某种类型的网络和文件 I/O。
本机内存不足行为与 Java 堆内存不足行为也不太一样,因为无法对本机堆分配进行单点控制。尽管所有 Java 堆分配都在 Java 内存管理系统控制之下,但任何本机代码(无论其位于 JVM、Java 类库还是应用程序代码中)都可能执行本机内存分配,而且会失败。尝试进行分配的代码然后会处理这种情况,无论设计人员的意图是什么:它可能通过 JNI 接口抛出一个 OutOfMemoryError,在屏幕上输出一条消息,发生无提示失败并在稍后再试一次,或者执行其他操作。
缺乏可预测行为意味着无法确定本机内存是否耗尽。相反,您需要使用来自操作系统和 Java 运行时的数据执行诊断。
本机内存耗尽示例为了帮助您了解本机内存耗尽如何影响您正使用的 Java 实现,本文的示例代码(参见 )中包含了一些 Java 程序,用于以不同方式触发本机堆耗尽。这些示例使用通过 C 语言编写的本机库来消耗所有本机地址空间,然后尝试执行一些使用本机内存的操作。提供的示例已经过编译,编译它们的指令包含在示例包的顶级目录下的 README.html 文件中。
com.ibm.jtc.demos.NativeMemoryGlutton 类提供了    gobbleMemory() 方法,它在一个循环中调用 malloc,直到几乎所有本机内存都已耗尽。完成任务之后,它通过以下方式输出分配给标准错误的字节数:
1
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 运行时版本捕获输出:
1
2
3
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 运行时版本为:
1
2
3
4
5
6
7
8
9
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 演示的输出如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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 运行时上执行相同的测试案例时,会生成以下控制台输出:
1
2
3
4
5
6
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 堆耗尽而抛出的错误的惟一区别在于消息。
尝试在本机内存耗尽时分配直接 ByteBuffercom.ibm.jtc.demos.DirectByteBufferUnderNativeStarvation    类尝试在地址空间耗尽时分配一个直接(也就是受本机支持的)java.nio.ByteBuffer 对象。当在 IBM Java 运行时上运行时,它生成以下输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 运行时上运行时,此测试案例生成以下控制台输出:
1
2
3
4
5
6
7
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)

返回列表