为了让俺这点可怜的经验更有针对性,先做一点背景说明:
1、我这里说的性能调优主要是由资深开发人员完成的,性能调优的结果就是代码的修改和优化。为什么说要资深开发人员来完成呢(^_^,本人也不是什么资深人员,赶鸭子上架),因为性能调优的过程一般伴随这性能瓶颈的分析,这需要对产品代码(尤其是核心模块的代码)比较熟悉而且比较有把握,这样才能既能比较快速的分析出性能瓶颈,又能在做代码修改的同时比较准确的估计代码的修改可能引起的问题,给测试人员提供比较有针对性的建议。性能调优的代码修改往往会引入让你大跌眼镜的bug
2、个人负责了两次性能调优,经验很浅。为了更方便大家参考,现在公司的产品是一个基于eclipse的IDE工具,纯代码行大约50万行,插件规模大约70个左右(当然,不包含测试插件、国际化资源插件等),基于的主要平台有:Eclipse平台、JDT、WTP 1.5。
一时也整理不出合适的思路,下面就想到那写到那吧。
【性能分析/监控工具的选择】
在做性能分析的时候,主要做两个方面的性能分析:时间占用瓶颈分析、内存占用瓶颈分析。工具自然是必不可少的,我手头常用的工具有BEA JRockit和JProfile两种。一般需要分析内存占用的时候,基本上是遇到内存溢出的问题了~_~,我们要分析的情况基本上两种:堆内存溢出、PermGen内存溢出。
堆内存溢出分析:大胆的相信工具吧。尤其是工具中提供的调用关系分析功能,能够帮你搞清楚到底那些对象在堆中一直持有,然后还可以方便看出特定类型的实例个数、实例的大小等等。如果你的产品中,底层的一些模型是基于xml建立的(尤其是可恶的dom),那就更要好好看看了~_~。堆内存泄漏分析的时候,一定要注意三个地方:一、-Xms -Xmx配置;二、垃圾回收策略的配置;三、所在机器的虚拟内存配置。要保持这三点统一,否则多次分析之间可能会有一定的误差,耗费不必要的人力
PermGen内存区溢出分析:工具分析的作用不大。为什么这么说呢?PermGen内存区域是存放类型信息的,类型被虚拟机卸载的几率是极其小的(具体可以参考我博客里面的相关随笔),所以性能调优的时候唯一比较有效的方式是将产品中的功能模块基本上都启动一下,然后大致测试出PermGen区域需要的峰值是多少,然后在产品手册中明确指明-XX:PermSize=** -XX:MaxPermSize**,这对用户最重要。(用户才不管你产品的技术实现怎样怎样呢,人家只要不发生内存泄漏就可以了 ~_~)
有关堆内存和PermGen内存调优的最重要成果应该是两点:一是避免内存溢出的问题;二是经过测试,然后总结出适合你产品的配置参数(包括堆内存配置参数、PermGen内存配置参数、垃圾回收策略参数),重点要关注一下垃圾回收的频率和单次回收时间之间做权衡,因为这会在无形中干扰你产品运行时候的性能表现。对于用户来说,人家只需要知道怎么配置一下就ok了,具体里面的原理怎么样等等,人家才懒得管呢!
系统资源(例如图片资源句柄等)的泄漏:有专门的工具可以看,相信工具!!!根据工具提供的信息,能够比较快速的定位到代码。为了维护优化的效果,最好是通知所有写代码的人,怎么避免这种泄漏,否则你刚优化好了,人家有给你泄漏了
时间占用内存分析:工具有一定的辅助作用,但是最重要的工具是自己打时间戳!!!。工具在用来分析时间占用的时候,一般会在两种情况下出现较大的偏差:一、函数调用的层次过深;二、待分析的时间占用过长,工具统计出来的时间占用会有明显的游离误差。工具的大致作用就是宏观上帮你确定一下可疑的点,然后具体的分析,最好是在代码里面自己打时间戳分析。(分析时间占用的时候,上帝都可能靠不住,但是时间戳System.out.println(System.currentTimeMillis())还是可以信任一把的!!!)
【测试环境】
测试环境一定要保持统一,个人觉得有如下几点需要注意:
1、测试机器的物理配置不要发生变化,而且关于虚拟内存的设置保持不变
2、测试工程需要保持一致
3、在验证优化效果时候,产品配置参数保持一致。如果想测试配置参数对性能的优化效果,那就只测试参数。
4、性能测试一般在产品开发较为稳定的时候进行。如果在产品还在大量代码开发的时候进行,往往很难保证优化的成果。你的性能调优就是针对某一时间点的产品,尽量不要边更新代码边做性能调优
【产品稳定性】
性能优化可能带来的影响范围一定要充分分析,提交给测试人员进行测试。宁可错杀,也不能漏网。个人的经验,整体的较大的性能优化往往带来产品底层代码的修改,如果让测试人员对产品全部进行测试吧,那人家肯定不干;这就需要你大致的确定出来影响范围,给测试人员一个可以接受的范围,让他们进行验证。如果性能优化后,性能是提到了,结果给产品中引入的严重的而且是隐藏很深的bug,那这个性能优化的意义又有多大呢???例如你引入的缓存策略,来缓存重量级的模型,时间是快了,但是如果你忽视了被缓存对象的更新策略,那么可能就会引起隐藏的逻辑错误....
【减少工具带来的入侵】
我们用测试平台(例如JRockit、JProfile)进行性能分析的时候,尽量避免测试平台对产品性能本身的影响。个人经验是,结合本次监控和远程监控两种方式混用。现在大部分专业的测试监控工具,一般都支持远程监控。
··············先去吃饭了,午饭后回来接着写。。。。。。。。。。
【基于插件的产品经常会遇到的内存占用瓶颈原因】
1、xml、emf等技术的引入
xml对内存占用来说绝对是个敏感字眼,尤其是dom实现方式,emf模型对象也类似。关于xml实现方式的选择倒是可以说道说道,什么时候采用dom的方式呢?个人觉得一是应用场景要求具有较为丰富的语义,这是需求决定的,往往没办法;二是文件不会太大,例如如果你的目标xml文件的规模是不确定的,那最好在用dom方式之前好好斟酌斟酌。
2、编辑器等重量级对象的内存泄漏!!!
在Eclipse里面有一些重量级别的对象,尤其是workbench中,对这些对象资源的释放一定要小心,否则带来的后果绝对是很严重的。例如测试编辑器相关资源的释放情况,你可以打开一次编辑器,用工具检测一下堆内存的实例对象的变化情况,然后关闭,再监测一把,这样就可以看看相关的未释放的资源,然后排查一下代码,找个合适的地方dispose掉。Eclipse中类似的场景很多,一定要小心....
3、缓存策略的不恰当使用
我们引入缓存策略的初衷往往是基于“以空间换时间”的概念出发的,那么一定要对空间的管理加倍小心,对一个性能敏感的产品软件来说,内存一定是最昂贵的资源。个人经验,首先要关注的是,你的缓存规模真正需要多大,例如如果你的缓冲池持有的大对象一直较多,而整个堆内存空间又不富裕,那么肯定会大大提升垃圾收集的频率,而且往往这种问题不仔细分析很难判断出来~_~ 其次要关注的是,你的缓存策略是否需要相应的实效策略、更新策略、销毁策略呢???
4、设计的考虑不周到
对于较为复杂的场景,我们完全可以模仿Eclipse中的设计做法,设计多个层面的对象。例如,我们可以在重量级模型的基础上搭建轻量级对象,很多场景下,我们的轻量级对象完全可以提供足够的信息了,而且如果对轻量级模型的进行缓存策略的代价也会明显小很多,这在JDT core、Eclipse一些底层模块中都有很类似的引用。Eclipse中的“有样学样”法则,不但是教会我们怎么去实现一个功能点,它的设计我们也可以去模仿。
5、内存参数配置不合理
有几个经验直接说一下吧。首先,有关PermGen区域大小的设定最好基于峰值的测量,过小会明显增大PermGen区域内存溢出的频率,过大会浪费宝贵的内存资源,挤占实例堆的空间,用工具检测就会发现,会明显增大堆空间的垃圾回收的次数,而且每次回收的效果并不显著;其次,关于垃圾回收策略的配置参数,一定要选取自己合适的。(各种策略都相关的文档说明);第三,为了有效的排查内存溢出的问题,最好默认配置一下XX:+HeapDumpOnOutOfMemoryError参数,方便排查。
【基于插件的产品经常会遇到的时间占用瓶颈原因】
1、Eclipse懒加载法则的违背
首先,有关扩展收集的问题。例如你在Host插件中定义了扩展点,里面有class属性制定了特定接口类型,有很多个Extension插件都提供了这种扩展。你在Host插件中收集扩展的时候,往往会不小心调用了IConfigurationElement.createExecutableExtension(String propertyName),这种不经意间的调用会导致很多类型的加载初始化+相应插件的启动,。。。。往往性能瓶颈出来了
其次,把懒加载法则进一步延伸,其实很多场景可以模仿Eclipse中代码的做法,利用代理对象+懒加载的方式。例如我们给类似Navigator视图准备一个模型,那完全可以在模型设计上就引入懒加载的思路
2、设计是否合理
对于做一个产品软件来说,如果整体架构设计出现了问题,那带来的性能问题可能是后面性能优化不能简单解决的。举一个假设的场景,例如你的产品中底层模型全面基于emf,那么emf的反序列化(模型解析)就是非常耗时,你可以引入缓存策略;其次,你可以在emf模型层面的基础上再搭建一层轻量级的模型(类似于JDT中的IType等),一方面为有些并不需要重量级模型的场景提供服务,另外可以作为重量级模型的代理使用...
从更高一点的层面来将,Eclipse以现在的规模(而且是java做的产品软件)性能还能接受,某种程度上得益于其micro kernel + core plug-ins + application plug-ins的架构,它的内核和核心插件规模是很小的,否则,可能性能上就要牺牲不少了...
3、Job的错误使用
例如过多的Job会明显加重系统线程管理、调度的开销。再排查时间占用瓶颈点的时候,不要以为使用了Job就肯定更快了,要再撒一眼,往往很多刚接触Eclipse Job的人狂用Job~_~
其实可能是ISchedulingRule机制的不正确使用,例如锁定的范围过大,动不动就锁定整个工作区等等。其实ISchedulingRule的使用和java中synchronized关键字的使用有异曲同工之妙,锁定范围过大往往会大大伤害性能,而且会造成不必要的线程死锁等问题
追踪这类问题,可以在发现产品运行感觉有点不流畅的情形下,挂起调试实例,排查一下各个线程都在搞什么名堂。。。。。。。。
4、监听器的错误使用
例如各种change listener的使用,排查一下里面是否有耗时的操作。很多监听器机制的实现者都提供了调试跟踪的机制,例如org.eclipse.jdt.core就提供了如下丰富的调试跟踪选项:
例如打开相应的调试跟踪选项,就可以把真个应用中的所有的IElementChangedListener和所耗时间等等都给他搞出来。很多Eclipse底层插件中都提供了很多跟踪调试选项,对开发人员做性能分析特别有用,省得自己去打过多不必要的时间戳了(以前还真发现跟踪调试选项有点画饼充饥的意思,但确实还是挺有用的~_~)
5、插件的依赖是否合理
对于你自己的插件所依赖的插件,需要有一定的了解,当然Eclipse默认提供的一些插件可以放松一点。举个例子,我遇到过一个开发人员为了使用WTP中一个很简单的功能,竟然依赖了wtp中的一个ui层面的插件,引起的效果那就。。。
不写了,脑子有点糊涂了。。。。。。。。。。
本博客中的所有文章、随笔除了标题中含有引用或者转载字样的,其他均为原创。转载请注明出处,谢谢!