又见OutOfMemory——一次内存溢出故障诊断全过程 | 聚沙成塔-小哈 outofmemory怎么修复

这是一个几月前的案例,问题比较典型,在分析和事后学习的过程中让我对本地内存溢出有了一定的了解。在此和大家分享。

先说一下背景,应用环境是AIX5.3+WebSphere6.0.2.37。在今年的一季度曾发生过几次OOM故障,当时通过几次内存参数优化,最后确定为“-Xgcprolicy:gencon –Xms512m –Xmx1280m –Xmn200m”,此后稳定了半年,直到此次再发生应用宕机。

赶到现场,发现profiles目录下生成有javacore和heapdump文件,Javacore的第一行“Cause of thread dump : Dump Event "systhrow" (00040000) Detail "java/lang/OutOfMemoryError" received”表明了宕机的原因是OOM,但是令我困惑的是这段内容:

0SECTION MEMINFO subcomponent dump routine

NULL =================================

1STHEAPFREE Bytes of Heap Space Free: 818cb70

1STHEAPALLOC Bytes of Heap Space Allocated: 20000000

在分配的512MB(十六进制20000000)的堆空间中,有129MB(818cb70)空闲空间,按理说,这种情况下不该发生OutOfMemory。就算有申请一个大对象,同时JVM堆的新生代由于碎片原因没有连续空间满足要求,那么应该发生堆扩展,所以此次内存溢出不是堆(Heap)溢出,GC日志的分析也支持了这一点。

既然Javacore无法得到有用信息,我把目光转向了SystemErr.log,在对应日期的地方,我发现了大量如下报错:

[8/26/10 14:12:57:860 GMT+08:00] 0000002f SystemErr R Exception in thread "WebContainer : 1" java.lang.RuntimeException: java.lang.OutOfMemoryError: Failed to create a thread: retVal -1073741830, errno 11

[8/26/10 14:12:57:860 GMT+08:00] 0000002f SystemErr R at com.ibm.io.async.ResultHandler.runEventProcessingLoop(ResultHandler.java:801)

[8/26/10 14:12:57:860 GMT+08:00] 0000002f SystemErr R at com.ibm.io.async.ResultHandler$2.run(ResultHandler.java:881)

[8/26/10 14:12:57:860 GMT+08:00] 0000002f SystemErr R at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1497)

[8/26/10 14:12:57:860 GMT+08:00] 0000002f SystemErr R Caused by: java.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:980)

at com.ibm.ws.util.ThreadPool.addThread(ThreadPool.java:630)

at com.ibm.ws.util.ThreadPool$3.run(ThreadPool.java:1148)

at com.ibm.ws.security.util.AccessController.doPrivileged(AccessController.java:63)

at com.ibm.ws.util.ThreadPool.execute(ThreadPool.java:1146)

at com.ibm.ws.util.ThreadPool.execute(ThreadPool.java:1040)

at com.ibm.ws.runtime.WSThreadPool.execute(WSThreadPool.java:151)

at com.ibm.io.async.ResultHandler.startHandler(ResultHandler.java:248)

at com.ibm.io.async.ResultHandler.runEventProcessingLoop(ResultHandler.java:570)

at com.ibm.io.async.ResultHandler$2.run(ResultHandler.java:881)

at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1497)

在事后的学习中,我知道“Java.lang.OutOfMemoryError: unable to create native thread” 这样的异常是在说,本地内存耗尽,从而新的线程无法创建。而在当时我第一感觉是操作系统参数设置问题,之前我曾写过一篇由于nofile参数导致Too many open file的故障。于是我运行如下命令

#lsattr -El sys0 -a maxuproc

maxuproc 128 Maximum number of PROCESSES allowed per user True

然后运行chgsys修改默认的128为1024,这里我犯了一个错误,WebSphere单个Server就是一个Java进程,错误日志里是不能创建一个thread,而非process,与Oracle会创建多个oracle进程不一样。果然两天后又出现了同样的问题。 这一次的SystemErr日志中,除了上述的内容,还多了

[8/24/10 9:55:19:813 GMT+08:00] 00000036 SystemErr R Exception in thread "WebContainer : 4" java.lang.RuntimeException: java.lang.OutOfMemoryError: Unable to allocate 8192 bytes of direct memory after 5 retries

[8/24/10 9:55:19:813 GMT+08:00] 00000036 SystemErr R at com.ibm.io.async.ResultHandler.runEventProcessingLoop(ResultHandler.java:801)

[8/24/10 9:55:19:813 GMT+08:00] 00000036 SystemErr R at com.ibm.io.async.ResultHandler$2.run(ResultHandler.java:881)

[8/24/10 9:55:19:813 GMT+08:00] 00000036 SystemErr R at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1497)

[8/24/10 9:55:19:813 GMT+08:00] 00000036 SystemErr R Caused by: java.lang.OutOfMemoryError: Unable to allocate 8192 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.ws.buffermgmt.impl.WsByteBufferPoolManagerImpl.allocateBufferDirect(WsByteBufferPoolManagerImpl.java:656)

at com.ibm.ws.buffermgmt.impl.WsByteBufferPoolManagerImpl.allocateCommon(WsByteBufferPoolManagerImpl.java:570)

at com.ibm.ws.buffermgmt.impl.WsByteBufferPoolManagerImpl.allocateDirect(WsByteBufferPoolManagerImpl.java:506)

at com.ibm.io.async.ResultHandler.runEventProcessingLoop(ResultHandler.java:498)

at com.ibm.io.async.ResultHandler$2.run(ResultHandler.java:881)

at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1497)

Caused by: java.lang.OutOfMemoryError

at sun.misc.Unsafe.allocateMemory(Native Method)

at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:184)

… 7 more

我们可以看到是由于DirectByteBuffer无法分配导致的内存溢出,而Native Method指明了这是“本地”的溢出。通过这两个关键字,我查到了IBM的一份BUG记录:PK31010: OUTOFMEMORYERROR DUE TO DIRECTBYTEBUFFER,但是我的版本已是最新,无奈继续搜寻。

在Troubleshooting native memory issues这份文档中,介绍了3种在WebSphere中最常见的导致OOM的原因。其中第二个DirectByteBuffer use就是我们问题的症结。

Java 1.4 中添加的新 I/O (NIO) 类引入了一种基于通道和缓冲区来执行 I/O 的新方式。就像 Java 堆上的内存支持 I/O 缓冲区一样,NIO 添加了对直接 ByteBuffer 的支持(使用java.nio.ByteBuffer.allocateDirect() 方法进行分配),ByteBuffer 受本机内存而不是 Java 堆支持。直接 ByteBuffer 可以直接传递到本机操作系统库函数,以执行 I/O — 这使这些函数在一些场景中要快得多,因为它们可以避免在 Java 堆与本机堆之间复制数据。

对于在何处存储直接 ByteBuffer 数据,很容易产生混淆。应用程序仍然在 Java 堆上使用一个对象来编排 I/O 操作,但持有该数据的缓冲区将保存在本机内存中,Java 堆对象仅包含对本机堆缓冲区的引用。非直接 ByteBuffer 将其数据保存在 Java 堆上的 byte[] 数组中。下图展示了直接与非直接 ByteBuffer 对象之间的区别:

直接与非直接 java.nio.ByteBuffer 的内存拓扑结构



直接 ByteBuffer 对象会自动清理本机缓冲区,但这个过程只能作为 Java 堆 GC 的一部分来执行,因此它们不会自动响应施加在本机堆上的压力。GC 仅在 Java 堆被填满,以至于无法为堆分配请求提供服务时发生,或者在 Java 应用程序中显式请求它发生(不建议采用这种方式,因为这可能导致性能问题)。

发生垃圾收集的情形可能是,本机堆被填满,并且一个或多个直接 ByteBuffers 适合于垃圾收集(并且可以被释放来腾出本机堆的空间),但 Java 堆几乎总是空的,所以不会发生垃圾收集。

摘自《理解JVM如何使用Windows和Linux上的本机内存》

解决此问题的方法,在文档中给出的是禁止异步A/O,通过在Web Container中设置参数来避免上节中所出现的由于Java堆空闲而不发生垃圾回收,导致本地堆撑满的情况。

Servers -> Application Servers -> serverName -> Web Container Settings -> Web Container -> Custom Properties:

Press New:

Add the following pair:

Name: com.ibm.ws.webcontainer.channelwritetype

Value: sync

Press OK, and then save the configuration.

添加此属性后应用又恢复正常,但是原先提高性能的特性反而导致内存溢出,违背了初衷,现在的做法只能算一个妥协。我会继续查找此问题的解决方法,最不济也要有个使用NIO的Best Practice。

文档中另两个容易导致本地堆OOM的原因是:

又见OutOfMemory——一次内存溢出故障诊断全过程 | 聚沙成塔-小哈 outofmemory怎么修复
过大的堆上限



我们知道32位机器单个进程可以访问的内存地址空间为4G,如右图所示,但实际情况下Windows系统由于内核态和用户态的划分,用户态只有2G的空间,Linux和AIX的可用空间大一点,但也在3G左右。,由于JVM实例进程寻址是一定的,所以Heap大小和Native Area此消彼长。而Native Area中有一部分就是供给线程的内存空间。

“应用程序中的每个线程都需要内存来存储器堆栈(用于在调用函数时持有局部变量并维护状态的内存区域)。每个 Java 线程都需要堆栈空间来运行。根据实现的不同,Java 线程可以分为本机线程和 Java 堆栈。除了堆栈空间,每个线程还需要为线程本地存储(thread-local storage)和内部数据结构提供一些本机内存。堆栈大小因 Java 实现和架构的不同而不同。一些实现支持为 Java 线程指定堆栈大小,其范围通常在 256KB 到 756KB 之间。”

1.5后一般线程堆栈大小为1M,“对于拥有数百个线程的应用程序来说,线程堆栈的总内存使用量可能非常大。如果运行的应用程序的线程数量比可用于处理它们的处理器数量多,效率通常很低,并且可能导致糟糕的性能和更高的内存占用”(摘自《理解JVM如何使用Windows和Linux上的本机内存》)

解决此问题的方法:设置恰当的JVM最大堆,32位不要超过1.5G;设置恰当的线程池大小(一般50~100),当线程使用较多时, 使用-Xss256k参数设置线程指定堆栈大小(IBM JDK还可以使用-Xmso ××k来设置Stack size for OS Threads 32-bit);更换64位的JDK。

第三个原因是线程池的TLS泄漏

这个现象我倒没见到过,如果你的thread dump里下面三个属性里的值有大于300,那么就要注意了

"WebContainer : 1003" (TID:0×37D62000

"Default : 338" (TID:109934D0

"TCPChannel.DCS : 303" (TID:0×4D720000

解决的方法是设置线程池的最小最大值一致。

  

爱华网本文地址 » http://www.aihuau.com/a/25101011/96300.html

更多阅读

汽车火花塞多久换一次 汽车火花塞故障现象

汽车火花塞多久换一次——简介汽车火花塞多久换一次,是不少购车者想要了解的问题,因为这对汽车的保养和使用有着很大的影响,那么,汽车火花塞多久换一次呢,请看详解。汽车火花塞多久换一次——汽车火花塞多久换一次汽车火花塞多久换一次

又见袁立本 又见白娘子

又见袁立本在第一次见到袁立本不久后的半个月,我们又一次见面了,这是应凤凰卫视之邀,拍老袁与知青们二十多年后相逢的镜头,为凤凰卫视八月底将播出的知青专题片《回家》的组成部分。老袁在上世纪八十年代任北京市委常委、市委秘书长之

一次劈剑的奇妙体验 亲子 奇妙的触觉体验

一次劈剑的奇妙体验相信大家都劈过剑吧!那么,大家在劈剑的过程中,肯定也遇到了不少问题。比如说:不知道怎么发力呀,老师怎么“纠”你的动作你也做不到位呀……我以前,也和大多数人一样,很“荣幸”的成为了这个“大家族”里面的一员:不知道

又一次看到“周杰伦江郎才尽了” 周杰伦江郎才尽

呵呵,是的,又一次看到了。这句话让我想到了很多,因为看到了太多次我想表达的观点真的太多,也不知此刻再写博文能够回忆起来多少,姑且记得多少写多少吧。1.你确定不是在从众找安慰?在qq群、百度贴吧以及一些论坛,差不多几年前开始(具体是几年

声明:《又见OutOfMemory——一次内存溢出故障诊断全过程 | 聚沙成塔-小哈 outofmemory怎么修复》为网友缺點诗意分享!如侵犯到您的合法权益请联系我们删除