1 引言
WAS(IBM WebSphere Application Server)是IBM发布的一款成熟的企业级Web中间件产品,凭借其可靠性与稳定性,一直是国内大型商业银行Web服务的主流选择。可再稳定也会出问题,在日常的生产运维中,WAS应用问题的排查确实让笔者这种银行运维人员头疼。一方面厂商提供技术支持的时效性与准确性有待改善,另一方面像IBM其他产品一样,网上开放的可参考和借鉴的资料太少,发生WAS问题时着实让人无从下手。不过不要紧,鲁迅先生曾经说过,“走的人多了,自然就有路了”,笔者作为具有多年WAS运维经验的老鸟,下面就把自己在应对WAS内存溢出方面的知识总结一下,为大家介绍一下如何优雅的应对WAS内存溢出。
2 IBM JAVA内存管理
要应对WAS内存溢出,必须对IBM对JAVA内存的管理有所了解,下面,笔者就简单介绍一下IBM是如何管理JAVA内存的。不同于大家经常使用的Oracle Java,WAS使用的JAVA是内置于WAS内部的IBM JAVA,与Oracle Java在JVM、配置参数等方面有着显著不同。
IBM JAVA 同样包含JDK、JRE、JVM三层,其关系如图所示:
图1 JDK、JRE、JVM关系
图2 JVM 运行时内存区域
程序计数器区域
Java虚拟机支持多线程运行,所以对于每个线程,都需要一个指示其运行程序位置的指针,这个指针指向当前程序运行方法的地址。
Java虚拟机栈
每一个Java线程都拥有一个私有的Java虚拟机栈。像其他传统语言一样,Java虚拟机栈保存了程序调用时的局部变量和部分结果(称之为Frame)。
方法区、运行时常量池
方法区存放运行时常量池、字段以及方法(包括构造方法、特殊方法)代码。在IBM Java 8版本中,所有加载的类都存放在称之为Metaspace的空间中,Metaspace使用操作系统本地内存空间。
本地方法区域
为了支持操作系统本地方法(如C语言)调用,虚拟机中在本地方法区域中存储本地方法调用的栈信息。
堆空间
堆是JVM运行时内存中最大的区域,也是和程序开发密切相关区域,所有的对象实例(包括基本类型)、数组都存放在这个区域。和传统的C、C++语言不同,Java语言不需要开发人员显式地进行内存的申请和释放,而是由JVM的Allocator(内存分配器)和Garbage Collection(内存垃圾回收器,简称GC)负责管理内存。我们最常见的内存溢出“java.lang.OutOfMemoryError : Java heap space”也主要和该区域有关。下面我们将着重阐述IBM J9 VM堆空间相关模型和垃圾回收策略。
堆空间内存结构和垃圾回收策略(GC)
J9 VM支持多种不同的GC策略,不同的GC策略对应不同的Heap内存模型及分配回收算法,不同的GC策略适应于不同的业务场景,对于大多数系统(特别是交易类系统)来说,可使用“Generational Concurrent Garbage Collector”策略(简称gencon,参数:-Xgcpolicy:gencon可以指定使用该策略),这也是J9 VM的默认GC策略,本文主要详细介绍该策略。
“Generational Concurrent Garbage Collector”策略特别适合存在非常多短生命周期对象的应用,即对象申请完之后,很快就不被使用,可以被GC回收。而一般的交易类系统,都符合这种场景。
在该策略下,Heap内存被划分成新区域(Nursery)、老区域(Tenured)。所有对象创建后都被分配到Nursery区域,之后如果该对象一直标记为可用,则会被自动到Tenured区域。
图3 J9 VM 默认堆空间内存模型
图4 Local GC过程
上文提到,在一般场景下,大部分对象创建后,很快就不被使用、存活的对象较少,所以Local GC移动的数据也很少,而且Local GC后,可以得到很大的Allocate空间,这样就减小了GC时间。在JVM中,GC意味着所有运行中线程都要停下来(Pause)等待GC结束,GC完成后,才可以继续运行,所以Local GC可以减少因GC带来的系统吞吐量下降的影响。
发生堆空间分配失败或者调用System.gc()方法后,触发Global GC过程。Global GC通过标记、清除、压缩过程来尽可能释放JVM内存空间。Global GC需要获得整个JVM的排他控制权,所以当进行Global GC时,所有应用线程也将暂停。当Global GC结束后,应用线程将恢复执行。
3 常见的WAS内存溢出原因
上面我们介绍了IBM Java内存管理的模型和策略。理解上述模型后,我们可以清楚的知道为何会发生内存溢出:
(1)JVM内部或者JVM间接使用的操作系统内存分配失败后触发内存溢出报错。JVM内存区域中,除了程序计数器区域外,Java虚拟机栈、堆空间、方法区、运行时常量池、本地方法栈都可能会发生内存溢出报错。
(2)对于堆空间,当堆空间已经尽可能扩展,并且JVM花费了95%以上的时间在GC时,也会触发内存溢出报错。
以上两点是内存溢出的基本要点,但实际生产系统由于运行环境往往较为复杂,在处理实际问题时,我们还应结合环境配置和业务场景来分析。通过总结实际运维过程中经验,可以将内存溢出原因分为如下几类:
(1)堆内存大小上限配置过低
由于Java程序所能使用的堆空间上限完全取决于JVM启动时的参数配置,当堆空间上限参数设置过低,即使操作系统物理内存空闲较多,应用程序也无法使用。所以在问题排查时,我们首先应该明确系统配置的堆空间上限(由Xmx参数指定),一般不能使用堆大小上限默认值。
(2)程序内存泄漏导致内存持续增长
如果程序存在内存泄漏,即使已经不再使用的内存仍将无法被GC回收释放,JVM内存将持续增长(而且,由于内存使用率逐渐升高,将会更加频繁的触发GC,反复GC又会引发CPU过高),最终导致堆内存空间满而引发内存溢出。
(3)数据查询交易返回记录数过多或者程序申请使用大内存对象
当程序过度地使用内存大对象或数组,导致无法申请足够的内存空间而引发内存溢出。例如,在实际生产中,可能存在应用程序读取整表数据或情况(数据条数在几万条以上),极易引发内存溢出。
(4)物理内存过低或因其他进程消耗过多内存引发内存溢出
即使我们设定了合理的JVM内存空间大小上限,但也有可能因为本地操作系统本身可用内存过低、无法实现内存空间的动态扩充,进而导致内存溢出;也可能因为在同一个操作系统上运行的其他JVM或者本地进程使用过多的内存导致内存溢出;由于JVM的部分区域(如Metaspace、DirectMemory等)直接使用的是操作系统内存,所以当操作系统内存过低,但创建本地线程过多、加载类过多时也有可能发生内存溢出异常;当程序过度使用DirectMemory也会引发内存溢出。
(5)交易量突然增大
如果我们将JVM堆内存上限设为M,每支交易处理需要使用的堆内存是N,那么当同时处理的交易量X突然增多N*X>M时,就容易触发内存溢出。
4 如何优雅的应对WAS内存溢出
当发生内存溢出后,首先要做的是恢复生产,恢复因内存溢出而宕机的Server。恢复生产后,可按照下面步骤进行内存溢出原因分析。
收集环境信息
内存溢出分析首先要做的就是收集环境信息和日志信息。
收集日志文件
表 1 收集日志文件表
分析应用日志
查看SystemOut.log日志java.lang.OutOfMemoryError的提示信息,确定内存溢出发生在JVM的哪个区域之后,查看SystemOut.log、SystemErr.log中应用交易日志,分析是否可疑的异常交易。
分析堆内存使用趋势
一般内存分析,第一步先查看JVM内存使用情况,即通过“IBM Pattern Modeling and Analysis Tool for Java Garbage Collector”工具,打开native_stderr.log文件,查看JVM堆空间内存使用曲线:
对于大对象或数组使用导致内存溢出的曲线一般如下图所示,存在曲线突然升高的情况:
图5 大对象内存溢出堆空间趋势图
内存泄漏导致内存溢出的曲线一般如下图所示,曲线缓慢上升(红色曲线):
图6 内存泄漏程序堆空间趋势图
找到堆空间可疑内存溢出点
分析线程现场信息
使用“IBM Thread and Monitor Dump Analyzer for Java”工具,分析javacore文件。检查内存溢出时正在执行的交易、正在执行的方法。
非堆空间内存溢出
如果出现“java.lang.OutOfMemoryError: 本机内存耗尽”内存溢出报错,则需要考虑DirectByteBuffer内存区域引发内存溢出。
5 如何在具体场景应用
图7:发生问题时某台WAS服务器的内存监控情况
第三步,首先我们来查看日志文件,下面分别是SystemOut.log和SystemErr.log的部分内容。果然,在问题时点附近的错误日志中看到了OutOfMemoryError,同时在应用日志中看到了一些正在执行的sql,那么到底是哪个程序在作怪,又是为什么产生了内存溢出呢。
图8:问题时点的应用日志
图9:问题时点的错误日志
第四步,看来仅从日志是无法定位具体问题的,笔者接下来要运用工具来解决问题了。笔者先后用IBM HeapAnalyzer和IBM Thread and Monitor Dump Analyzer for Java工具,分别对Heapdump文件及Javacore文件进行了具体的分析。对Heapdump文件的解析结果显示,某个List居然存在68万多个对象,占用了近50%的内存空间。对Javacore文件的分析结果显示,发生溢出时某支交易线程一直处于等待状态。
图10:Heapdump文件的分析结果
图11:Heapdump文件的分析结果
6 如何预防或解决内存溢出问题
7 最后