Java性能调优方法:基于等待的调优(四)
基于技术的等待点
基于层次的等待点考虑的是在不同服务器之间传递请求,而基于技术的等待点关注的则是在单个服务器中如何通过有效地内部工作来传递请求。基于层次的调优,类似于IBM的队列调优,只是调整应用的有效第一步,如果忽略了调优应用服务器的内部工作,则会对应用性能产生巨大的影响。这就类似于调整JDBC连接池以发送最佳数量的负载给数据库,但是忽略了检查执行的SQL语句——如果查询需要连接十个表单,每个表单有一百万个记录,则最佳负载可能是两个连接,但是如果我们优化了查询,则数据库可能支持二百个连接。深入研究应用服务器和应用使用的潜在技术,可能存在以下通用的基于技术的等待点:
● 池对象(比如无状态session bean或者其他应用放入池的业务对象)
● 缓存设施
● 持久化存储 或外部依赖池
● 通讯基础设施
● 垃圾收集
大多数情况下,无状态session bean池的大小被应用服务器优化,不会是一个明显的等待点,除非池大小被手工错误的配置了。但是也存在一些池对象必须手动配置大小——这些可能成为有效的等待点。当一个应用需要一个池化的资源,它必须从池里获取一个资源实例,使用它,然后释放给池。如果池太小,所有的对象实例都在被使用,则请求不得不等待一个实例可用。显而易见,等待一个池化的资源会增加响应时间,如果越来越多的请求被堵塞在等待池化资源,会导致明显的性能下降。另一方面,如果池很大,它可能消耗过多内存,对总体JVM的性能产生差的影响。池的最佳大小需要权衡,只能在对池的利用情况做彻底的分析才能决定。
池化对象是无状态的,这意味着应用从池中得到哪个实例都无所谓——任何实例都行。另一方面,缓存的对象都是有状态的,也就是说当应用从缓存中请求一个对象时,它的目标是一个特定对象。举一个很粗糙的类比说明一下区别:考虑人们生活当中两种常见的活动:超市购物和接孩子放学。在超市中,任何收银员都可以接待每一位顾客,无论顾客选择哪位收银员都可以顺利结账。因此收银员可以池化。但是在接孩子放学时,每一个父母只想要他们自己的孩子,别的孩子是不行的。因此孩子可以被缓存。
如前面所述,缓存提出了一个新的调优挑战。简单说,缓存的目的是在本地内存中存储对象,使应用可以随时读取它们,而不是在需要的时候才获取他们。一个合适大小的缓存可以对通过远程调用加载对象的行为提供明显的性能改善。但是,一个不合适大小的缓存,可能产生明显的性能阻碍。因为缓存维护有状态的对象,所以重要的一点是在缓存中存储最频繁访问的对象,同时保留额外的空间来处理非频繁访问对象。试想如果缓存太小,请求会怎样:
1、请求检查缓存中是否存在某对象,结果失败。
2、请求需要查询外部资源获取对象数据。
3、因为缓存通常维护最频繁访问的数据,所以这个新对象需要添加到缓存中(它正在被访问)。
4、但是如果缓存满了,必须利用“最少最近访问”算法选择一个对象移除。
5、如果缓存对象的状态与外部资源不一致,则缓存对象移除之前必须更新外部资源。
6、新的对象此刻添加到缓存中。
7、新的对象最终返回给请求。
这是一个低效的过程,如果大多数请求都要执行这些步骤,那么缓存肯定会降低性能。缓存必须调整到足够大以最小化缓存的“不命中率”,一次不命中意味着需要执行前面提到的七个步骤,但是也不能太大导致占用太多JVM内存。如果缓存需要非常非常大才能满足性能需要,那么最好是重新考虑被缓存对象的实质和它们到底是否值得缓存。
类似对象池,外部资源池,比如数据库连接池,也必须足够大以满足请求不会被迫等待池中的一个连接变为可用状态,但是也不能太大,导致应用浪费外部资源。“后退调优”一节讨论了如何决定这些池的最佳大小,但是在本节的上下文中,牢记它们代表了一个明显的等待点。
调优通讯基础设施远远超出了本文讨论的范围,因为其具体实现因产品不同而存在明显的区别,这包括诸如MSMQ、MQSeries、TIBCO等主流产品。但是请记住,如果一个应用与某消息服务器交互,它必须经过合适的调优或者它也代表了一个等待点。
最后一个明显影响JVM性能的等待点是垃圾收集。它不太适用本文中描述的等待点分析过程(检查一个请求,定位导致该请求等待的技术),但是由于它可以对性能产生显著的影响,所以把它列在这里。不同的JVM实现和不同的垃圾收集策略决定了垃圾收集如何执行,但是在很多情况下,一次主垃圾收集(或者说标记—清除—压缩垃圾收集)可能导致整个JVM暂停直到垃圾收集完成。一个显著提高JVM性能的办法就是优化垃圾收集。如果想了解更多垃圾收集的信息,请加入GeekCap讨论应用基础设施调优。