Websphere性能分析与优化
——从Heapdump浅谈JVM堆设置
不同版本的JDK可以设置的JVM堆大小是不一样的,而JVM堆的大小直接制约系统的性能,合理设置每个应用服务器中的JVM堆,在系统性能优化中是十分关键的一步。
一般来说,JVM堆可设置的大小受其版本限制,可分为以下两大类:
1、32位的JDK,JVM堆最大可设置到1.5G左右
2、64位的JDK,JVM堆大小暂无限制
那我们该如何调整JVM的堆大小呢?在Was上如何去设定一个合理的值且多大的值才算是合理的呢?
首先我们来了解下JVM堆大小对系统有哪些主要的影响,在JVM堆不足的情况下将会导致系统:
1、频繁的垃圾回收(引发系统资源紧张情况,集群环境下CPU资源消耗就更严重)
2、OOM,内存溢出(out of memory)
系统繁忙时,一般都是在处理大量的客户端请求,或是在进行多个复杂的计算,它们都需要向JVM堆申请空间进行对象的创建。在堆空间不足的情况下,应用系统会出现以下一些情况,从而大大降低客户的感知度:
1、请求操作响应时间长
2、请求操作失败,资源等待操作,内存溢出
为了保证系统的性能,提高系统稳定性,我们就需要对JVM堆的详细使用情况刨根问底,以此估出一个合理的值来设置JVM堆大小。
有专家给出建议,Was每个Server的线程池不宜配置过大,一般建议值在50-120之间,而JVM堆则设置在2G内。这个建议针对大部分系统都是适用的,如果在这个配置上系统运行还出现性能问题,可先从应用程序角度着手优化。因为无论线程池的线程大小是多少,每个线程给系统带来的主要压力就是JVM堆资源的占用。
在32位的Java虚拟机上,JVM堆最大可设置到1.5G左右。假设请求从客户端来到Was,Was从线程池中分配一个线程处理这个请求,同时从JVM堆空间申请相应的资源进行操作。假设这是一个上传5MB的Excel的线程,那么在上传与处理这个Excel过程中,线程占用的JVM堆的资源会越来越多,甚至有可能需要向JVM堆申请超过30MB的空间(当然30MB的堆空间不是绝对,这与代码设计密切相关,如果到Excel上传过程中,还要进行分析,封装,持久化等操作)。这种情况下,再有50个类似的上传Excel的线程,系统性能就会受到影响,因为在线程操作结束前,JVM堆资源被大量占用且无法快速释放,系统剩余可分配的JVM堆空间越来越少,如再有其它线程继续申请堆空间资源的话,就需要等待垃圾回收或者资源空间的创建了。
因此为了保证系统的性能,我们首先要保证JVM堆剩余可分配空间的大小。除了加大JVM堆的设置外(可考虑集群方式降低单Server的压力),我们还要从系统设计与应用程序代码优化入手,避免资源相互占用,资源调用后可快速释放。
应 如何优化应用代码呢?在实际项目中,许多应用系统的工期都十分紧张,从需求调研到系统上线,可能仅有个把月的时间。由于复杂的业务逻辑和紧张的工作期限, 在编码过程中难免都会出现一些漏洞,这些漏洞问题可能因为功能交叉关联,过于复杂,在测试阶段不能重现,直到投入生产使用中才发现,并且随着系统功能不断 增加,关联越来越多,有些问题会成为影响性能的根源,让我们猝不及防!因此我们需要增加数据监控这一过程,其中可以通过Heapdump文件收集生产上每个Server的JVM堆中对象空间详细分配情况作为参考,进行代码优化与堆大小设置。
Heapdump文件主要用于记录JVM堆对象的详细信息,在JVM堆接收到转储命令时产生,其相当于JVM堆某一时间切面的详细信息,也可理解为记录对象在JVM中详细痕迹的一个日志。通过分析Heapdump文件,我们可了解到各个对象的大小,及对象之间的关联关系等。
Heapdump文件一般比较大,文本查看工具已不能满足我们的要求,为了迅速从文件中找占用资源最多的对象,我们需要借助一些专业的工具来进行分析。如:
1、 IBM HeapAnalyzer
该软件采用树形方式描述JVM堆,目前已出到V4.08版本。分析过程需要一层层展开查阅,寻找到资源消耗最大的关键对象。
通过该工具,我们可以详细了解到对象之间的关系,根据关系推断资源消耗大的对象主要存储了哪些内容,由哪些关键类去触发。
在左图中我们看到庞大的堆栈树,若一层层展开查阅与分析,很容易就看到眼冒金星。因此我们需要使用一些小功能去帮助分析整个堆载信息,如:
(1)Go to the largest drop in the subtrees (使用该功能,可快速查询到堆栈树下最大的可疑对象,然后逐层向上分析)
(2)List same type(查看相同类型的对象,在死循环情况下,可以快速定位问题)
2、 MDD4J工具
同样是分析内存堆使用情况的工具,但较HeapAnalyzer有较大不同,该工具会在装载文件的过程中进行智能分析,然后给出相应的建议,缩小我们的分析范围。
如:可疑内存对象视图、类型视图、实例视图、树形视图等。
简易分析方法:
(1)先通过可疑内存对象视图,了解到究竟哪些对象存在泄露的可能性
(2)再通过树形视图详细分析每个可疑泄露对象,展开每一层节点,分析是否存在内存泄露的可能性。一般来说,我们都是根据经验查找非JDK,或者IBM对象的对象,而直接查找项目或项目使用到的组件的对象进行分析,查看其关系。
以上两种工具都可以从IBM的官方网站下载,也可以安装ISA(IBM Support Assistant Workbench )分析工具,快速搜索最新版本下载。如:
无论是哪个工具,通过其提供的信息,我们可认识到JVM堆中的内容就是一棵庞大的堆栈树,我们可先排除Was中间件的问题,从项目代码或者使用到的组件代码进行分析,搜索可疑泄漏对象,一步步详细分析下去,关键步骤就是要找到以下一些重要对象:
序号 |
名称 |
描 述 |
1 |
可疑泄漏对象 |
可疑泄漏对象是被怀疑导致内存泄漏的对象。可疑泄漏对象通常是泄漏源。如:org/hibernate/util/SoftLimitMRUCache |
2 |
泄漏源 |
泄漏源是一个对象,它对指向泄漏容器的对象链进行引用。如果在所有者链中找不到任何类对象,那么表明此对象是内存转储中的根对象,从这个对象可以到达泄漏容器 |
3 |
泄漏容器 |
|
4 |
泄漏单元 |
|
收集到以上信息后,我们就可以进一步结合项目代码去分析问题所在。
为了保证分析质量,我们需要采集连续多个时间段的Heapdump文件。因为有可能在一些复杂操作过程中,所需创建的对象比较多,但它们最终会被垃圾回收的功能回收,因此它们并不一定是触发性能问题的主要根源,而是在大并发请求或者资源不足的情况才会引起性能问题。
分析Heapdump文件时,并非每个可疑泄漏对象都有问题,我们要分析与检查每个泄漏源堆栈前后所涉及的对象,通过对象的大小与业务逻辑分析,判断这些对象设计与引用是否合理。在JVM堆中主要存储了两种对象,一种是临时对象,一种是永久保存的对象。临时对象在失去引用的情况下,才会由垃圾回收功能回收空间。而永久保存对象,从JVM的进程创建的开始,就一直存储在JVM堆中,直到进程结束后才释放。因此我们检查与分析Heapdump文件过程中,可以结合项目代码进行判断。
我们可以通过Heapdump生成方式的不同,采用不同策略去检查与分析。
由内存泄露产生的Heapdump主要有以下几种原因:
l JVM堆空间不足(内存不足)
l 死锁,程序死循环导致与线程资源相互占用
l 内部错误
分析过程我们可检查是否存在死锁问题,再检查堆空间对象大小是否合理。
手工生成Heapdump文件,通常都是作为健康检查,或者对JVM堆使用情况分析,建议通过比较多个版本的Heapdump文件,了解在整个JVM中究竟哪些对象占用了空间,他们的泄露源是什么,我们从以下方面进行分析:
l 静态变量:将对象存入静态变量中实现缓存是常见的手段,其优势是避免了资源的重复读取,但是不断将对象保存到静态变量而没有显示释放,容易导致内存溢出,或者剩余可用堆空间不足(所以有些系统虽然进行了垃圾回收的优化,但性能提升不明显,这主要是因为堆空间可用率不高);
l Threadlocal:Was采用了连接池方式,web线程的管理是由Was负责的,如果大量使用Threadlocal来存储临时对象,并且在线程退出的时候不显示销毁,也会导致JVM堆可用空间不断减少,其结果和第一点一样;
l 类加载问题:每次不重新启动Sever,而是直接重新启动应用,多次操作后JVM堆剩余空间越来越少,进而引发内存泄露问题(这主要是应用程序中存在静态对象在类装载过程建立了引用的关系,即使停止了应用,但是由于引用关系未显示释放,导致对象一直残留在JVM堆中,只有重启Server才可以销毁) 。
给JVM堆设置一个合理的数值,不一定能给系统的性能带来巨大飞跃,但这却是系统性能调优过程中必不可少的一步。如果系统要运行得更加稳定与支撑更大的并发请求操作,我们需要从各方面着手检查与优化,Heapdump文件可以给到我们很大的帮助。结合垃圾回收日志,我们就可以轻松掌握到JVM堆详细情况,以便为系统设置合理的堆值。