Java EE应用中的性能问题解决方案 — 第一部分 内存溢出和JVM内存管理内幕(B)

声明:本文禁止未经本人同意的任何形式转载!如有转载需求,可与本人通过个人资料中的电子邮箱联系。对于未经同意的转载,本人将保留进一步行动的权利!

IBM的JVM内存管理 IBM的JVM稍有不同。与其从一个大的分片堆开始,它将所有的对象保留在单一空间中并在堆增长时释放内存。它通过运行不同层级的垃圾回收实现。主要的表现是:堆开始时相对较小,被填满,并在某时间点上执行简洁型的标记清扫垃圾回收来清除生命周期完毕的对象并在堆的末端对活动对象进行压缩。当堆逐步增长时,长期活动的对象被不断推向堆的末端。所以断定潜在的内存泄露的最好途径是观察堆的完整动作:堆有不断向上增长的趋势么?

解决内存泄露的问题 内存泄露问题比较复杂,但是如果能诊断出导致内存泄露的请求,那么问题就简单一些。将应用放到开发环境中,并在内存分析器模式下运行,并执行以下步骤: 1、 在内存分析器中启动应用 2、 执行用例(发出请求) 3、 记录堆情况,在再次执行用例前获得堆中的所有对象的信息 4、 再次执行用例 5、 再次记录堆情况 6、 对比两次堆的情况,寻找在用例执行完成后堆中本不应该出现的对象

现在,就可以结合源代码来查看是否有对象产生了内存泄露,或是有意识地将这些对象保留在内存中的行为。

如果前面6步完成后并没有明确内存泄露的问题,有一个办法就是将第4步执行n遍,希望在n遍后的内存中出现n个或者n的倍数个的一些内容。虽然这个办法并非总能奏效,但却能最大程度帮助寻找内存泄露的对象。

如果不能将内存泄露从一个特定的请求中分离出来,可以有以下的选择: • 分析可疑的请求直至找出内存泄露 • 配置有内存功能的监控工具

第一种方法对小型应用还尚可行,但对于大型应用来说就不适用了。第二种方案更高效如果能够访问到监控工具的话。这些工具使用字节码工具追踪对象创建和销毁的计数,并通常报告预定义的和用户自定义的类的对象个数,例如集合框架的类。例如,监控工具可能会报告/action/login.do请求完成后留下了100个HashMap的对象。虽然这样并没有明确内存泄露发生的地方,但至少已经通过较小的额外开销了解到需要到内存分析器中仔细查看哪些请求。在生产环境下不能宕机而需要找出内存泄露的地方是很苛刻的,所以就需要使用这些工具来降低一下工作的复杂度。

所有这些背景资料都为我们判断内存泄露提供了资料。在Java中,内存泄露是一个对象保留了对另一个并不需要的对象的引用,因此阻止了垃圾收集对空间的回收。在Sun的JVM架构中,未被解除引用的对象将从Eden和两个存活空间移至老生代。试想,如果在多用户的环境下,多个请求执行了内存泄露的代码,我们就能观察到老生代的增长。

下图展现了可能导致内存泄露的对象:灰色的,逃过了几次大规模收集的老生代对象。并非老生代中的所有对象都是内存泄露的对象,但发生了内存泄露的对象最终会出现在老生代中。如果内存泄露真实存在,老生代就会被内存泄露的对象占据,直至内存溢出。

 

因此,我们必须设法追踪老生代的垃圾回收效率:每当大规模垃圾回收发生时,能回收多少内存?老生代中的内存是否是按照可识别的模式增长?
 
可以通过监控API来获得一些信息,当然细节的信息要通过详细垃圾收集日志来获取。日志记录的级别可能会影响JVM的性能(记录越详细,性能损失越大)。为了判断是否存在内存泄露,可以采用相对比较标准的设置来查看各个不同的“代”(分片)中的内存在垃圾回收之间的情况来做出相应的判断。Sun的文档中显示这种记录级别可能会带来5%的额外开销:
 
 
观察堆的总体趋势能发现可能的内存泄露,但是明确观察老生代的增长率才是更加权威的判断。
声明:本文禁止未经本人同意的任何形式转载!如有转载需求,可与本人通过个人资料中的电子邮箱联系。对于未经同意的转载,本人将保留进一步行动的权利!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章