基于WebSphere 构建的企业应用,时常会出现性能问题,在严重的情况下还会提示出内存溢出,这是一件很让人恼怒的事情。在WebSphere Application Server(Was)运行的时候,内存溢出,会生成大量的溢出文件,如Javacore, Heapdump等文件,占用了大量的磁盘空间。在这种情况下,时常会出现一连串的系统问题,如部署在Was的所有应用服务都报错,Was连控制台也无法访问等。
为解决问题,我们通常会选择重新启动整个Was或者服务器,然后分析运行日志SystemOut.log、ystemErr.log、ative_stdout.log、native_stderr.log 和系统内存溢出的时候产生的Javacore、Heapdump文件来寻找出问题。
那么,为什么会出现内存溢出呢?
应用服务器在运行过程中需要创建很多对象,而在应用服务器的堆空间大小有限的情况下,请求进程不断申请空间来创建与存放对象,在达到上限时而服务器又没能释放出空间来处理申请空间的请求就会出现内存溢出情况。这就像吹气球,当气球中的气体到达一定程度时,气球就会被撑爆。(32位的JDK的Jvm堆空间分配最大支持1.5G的大小,超过则无法正常启动。而64位的JDK堆大小分配无限制,其大小受到服务器的内存限制。)通常在投入生产的系统中,出现溢出一般都是对象分配不合理导致的。
在此,让我们先了解下Java世界里,对象与对象管理是怎么一回事。
在Java的体系中,所有的类作为一个对象(包括Jdk本身提供的类,应用中由开发人员编写的类),都是直接或者间接继承了Java.lang.object产生的。这些类被创建的时候都会向Jvm堆申请一定的内存空间存放,因此在Jvm堆空间里会存放各式各样的对象,有的是静态类型,有的是私有类型等等,而这些对象都是通过垃圾收集器进行管理的。
垃圾收集器(Garbage Collection,我们通常称之为GC),它是Java与C++语言的一个显著的不同点。Java语言通过垃圾收集器管理内存中的对象,垃圾收集器是在C++基础上的一种极大进步,使许多编程问题消失于无形中。通过垃圾回收,在Java编程过程中,内存漏洞的情况会少得多。
虽然垃圾收集器为开发带来了极大的便利,但如果对象分配与使用不合理,也会导致垃圾回收在性能上拖Jvm的后腿。为了解决性能问题,GC则是一个让我们不能忽视的问题。根据不同的应用场景,选择适合的GC策略,可以帮助系统性能在一定程度上得到显著的优化。
垃圾收集器为了快速清除在JVM堆中不再使用的对象,从而释放出空间来供其他请求创建对象,常见的方法有引用计数方法和对象引用遍历方法等算法,目前主流的方法为对象引用遍历方法。通过这些高效的算法实现的GC策略有许多种,他们都是针对各种应用场景而产生的,我们可以通过在JVM启动参数中设置,来调整GC策略。
在IBM SDK 5.0提供了四种不同的GC策略优化配置(IBM 在WebSphere 6.1版本开始,IBM JDK 升级到IBM SDK5.0,也就是常说的JDK1.5),详细如下:
序号 |
策略 |
选项 |
描述 |
备注 |
1 |
针对吞吐量进行优化 |
-Xgcpolicy:optthruput |
WAS默认策略。对于吞吐量比短暂的 GC 停顿更重要的应用程序,通常使用这种策略。每当进行垃圾收集时,应用程序都会停顿。 |
|
2 |
针对停顿时间进行优化 |
-Xgcpolicy:optavgpause |
通过并发地执行一部分垃圾收集,在高吞吐量和短 GC 停顿之间进行折中。应用程序停顿的时间更短。 |
|
3 |
分代并发 |
-Xgcpolicy:gencon |
以不同方式处理短期存活的对象和长期存活的对象。采用这种策略时,具有许多短期存活对象的应用程序会表现出更短的停顿时间,同时仍然产生很好的吞吐量。 |
推荐使用 |
4 |
子池 |
-Xgcpolicy:subpool |
采用与默认策略相似的算法,但是采用一种比较适合多处理器计算机的分配策略。建议对于有 16 个或更多处理器的 SMP 计算机使用这种策略。这种策略只能在 IBM pSeries® 和 zSeries® 平台上使用。需要扩展到大型计算机上的应用程序可以从这种策略中受益。 |
|
在Sun Jvm也有自己特色的GC策略,如:
序号 |
策略 |
选项 |
描述 |
备注 |
1 |
并发收集器 |
-XX:+UseConcMarkSweepGC |
并发收集器与应用程序同时运行。这些收集器在某点上(比如压缩时),一般都不得不停止其他操作,以完成特定的任务,但是因为其他应用程序可进行其他的后台操作,所以中断其他处理的实际时间大大降低。 |
|
2 |
并行收集器 |
-XX:+UseParallelGC |
并行收集器使用某种传统的算法,并使用多线程并行地执行它们的工作。在多cpu机器上使用多线程技术可以显著的提高java应用程序区性的可扩展性。 |
|
3 |
串行收集器 |
-XX:+UseSerialGC |
用单线程处理所有垃圾回收工作,因为无需多线程交互,所以效率比较高。但是,也无法使用多处理器的优势,所以此收集器适合单处理器机器。当然,此收集器也可以用在小数据量(100M左右)情况下的多处理器机器上。 |
|
通过以上的列表数据,大家也接触到了各种常见的GC策略,相信到此对GC与GC策略也有了一定程度的了解。那如何衡量GC的性能,如何为何业务应用选择合适的GC策略呢?
一般来说,衡量GC效率的参数主要有2类,吞吐量和停顿时间。
吞吐量是应用程序处理的数据量。衡量吞吐量的标准是与具体应用程序相关的。
停顿时间是垃圾收集器将所有应用程序线程停下来,从而对堆进行收集所经历的时间。
如何分析和选择适合的GC策略呢?我们可以在业务应用系统打开详细垃圾回收,收集系统在运行过程中的GC日志,通过分析GC日志,来判断当前GC策略是否符合当前应用系统。
在实际应用场景中,不是每个应用服务都必须调整GC,如果您寄予厚望希望调了GC策略后,就一定能使系统达到性能上的提升,成效未必尽入人意。因此GC策略调整只是从GC角度去辅助性能优化,而调优的效果需要根据实际情况才能断定。例如:在测试机调整了GC策略,使用压力测试工具进行压力测试,性能优化成效显著;但在生产系统上调整了参数,却未收到让人满意成效,且生产机比测试机配置高,这些问题已经是屡见不鲜了,问题原因有很多方面,通常是因为生产实际使用场景与测试机压力测试场景不一致,系统受到的压力不一样所导致的。
但GC调整在系统能够性能调优上确是一个必不可少的环节,合理的创建使用对象,选择适合的GC策略,往往能让性能有一定的提高,也减少了内存溢出的可能性,或者缩短系统停顿的时间。
以下是一段发生在IBM SDK5.0版本上配置了分代并发策略所产生的GC日志,让我们一起看下如何分析这一段GC:
|
距离上次GC间隔时间为6.787 秒 Type=“nursery” :这次GC的类型是新生代方式,因为新生代的分配失败而进行垃圾回收了。 Id=”3365”代表GC发生在新生代,已经重复执行了3365次 timestamp:GC的执行时间 -->
这里记录需要申请的堆大小为710824b 约694KB -->
准备开始前 使用时间为 0.224秒 -->
前堆空间使用情况,可以看到剩余567408B,不能满足空间的申请要求了,申请空间是710824B,因此就需要进行GC -->
后堆空间使用情况 --> 详细gc情况 type="scavenger":垃圾回收类型为清理过程。 id="3365": 意为执行力3365次清理,并且没有发生过全局GC,如 。
-->
后的情况 nursery区 1、清理了出空闲堆大小为39108112b 约为38191.515625KB,约为37MB 2、 总大小为44144640B,约43110KB 约为42MB -->
Tenured长存区总大小为1365881856B,约为1.27GB 空闲堆大小为 651143448B 620MB -->
|
|
打开详细垃圾回收,我们就可以获得详细垃圾回收情况,根据以上的GC日志的分析,我们则可以了解到系统GC提供了什么样的信息给我们。通过这些信息,我们可以分析得出当前GC策略是否需要调整。(当然这个与您实际生产得出的GC日志分析结果有关)。
在分析的过程里,我们需要关注几个点,来分析GC策略是否需要调整:
l GC频率:多长时间执行一次GC,如果GC频繁的话,则需要检查系统现在的运行情况是否合理
l GC类型+GC申请空间大小+新生代空间大小:分析GC是否合理
l 各分代区域的大小:检查当前JVM堆的使用是否合理,新生代的空间大小是否符合当前业务场景要求。长存区的空间大小是否合理,再联合JVM配置的堆配置大小分析,堆的使用是否存在问题,因为虽然进行了GC,但堆空间的使用如不能满足要求的话,易导致系统出现全局GC,在压力过大的情况下还会内存溢出。
l 重点检查GC过程中长存区的变化:是否存在大对象,如果有的话,可以考虑-Xlp参数优化
l GC过程中是否清理了过多引用对象:如有则需要检查应用
l GC整个过程消耗的时间是否过长:过长的GC会直接影响到系统的性能,当然这个也GC类型有关,全局GC,清理长存区的GC时间相对会长点,但是这些类型的GC不会很频繁。
以上主要是通过详细分析GC日志,从而协助我们进一步了解到JVM堆的使用情况,最后得出当前GC策略是否符合业务应用使用场景,而更换其他GC策略则需要配合当前业务场景与JVM堆使用情况去选择适合的GC策略。
JDK的版本不同,GC的策略配置也有所不同,但这不是重点,如何读懂GC日志,分析JVM堆的使用情况后,更根据问题去寻找适合的JVM选项参数进行设置,从各方面去调整最终达到一个性能调优的目地。
由此可见,垃圾回收的调整在性能调优过程中也是一个十分重要的环节。
其他相关资料
适合IBM JDK 设置参数简单介绍:
序号 |
选项 |
描述 |
备注 |
1 |
-Xlp |
此设置与IBM JVM配合使用,以使用大页16MB来分配堆 |
|
2 |
Xlp64k |
此参数与IBM JVM配合使用,以使用64K字节页大小来分配堆 |
|
3 |
-Xnoclassgc |
关闭类垃圾回收,可以消除由于多次装入和卸载同一个类而造成地开销 |
|
4 |
-Xnocompactgc |
该参数完全关闭压缩。虽然在性能方面有短期的好处,最终应用程序堆将变得支离破碎,即使堆中有足够的自由空间也会导致 OutOfMemory 错误 |
|
5 |
-Xcompactgc |
使用该参数将导致每个垃圾收集周期都执行压缩,无论是否有必要。JVM 在压缩时要做大量的决策,在普通模式下会推迟压缩 |
|
6 |
-Xgcthreads |
该参数控制 JVM 在启动过程中创建的垃圾收集帮助器线程个数。对于 N-处理器机器,默认的线程数为 N-1。这些线程提供并行标记和并行清理模式中的并行机制 |
|
7 |
…. |
…. |
|
适合Sun Jvm 辅助配置相关参数简单介绍:
序号 |
选项 |
描述 |
备注 |
1 |
-XX:+PrintGC |
输入gc收集概括。输出形式: [GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]
|
|
2 |
-XX:+PrintGCDetails |
输出Gc明细情况,输出形式: [GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]
|
|
3 |
-XX:+PrintGCTimeStamps |
输出GC时间,输出形式: 11.851: [GC 98328K->93620K(130112K), 0.0082960 secs] |
|
4 |
-Xloggc:filename |
指定输出记录文件 |
|
5 |
-XX:+PrintGCApplicationConcurrentTime: |
打印每次垃圾回收前,程序未中断的执行时间, 输出形式:Application time: 0.5291524 seconds |
|
6 |
-XX:+PrintGCApplicationStoppedTime |
打印垃圾回收期间程序暂停的时间。可与上面混合使用 |
|
7 |
-XX:+PrintHeapAtGC |
打印GC前后的详细堆栈信息 |
|
8 |
-verbose:gc |
详细垃圾收集信息 |
|