最近用JProfile测试一个比较大的工程,希望能找到一些程序运行的瓶颈。过去使用Hibernate,很多人反映效率低。特别是懒加载关闭的时候,对象的持续生成最后会导致JVM直接OutOfMemory错误。目前使用iBatis,发现临时对象还是特别多,一开始百思不解。通过使用JProfile以后,终于找到了原因所在。
对象引用关系:baseView ->InstanceModel -> City
其中,baseView 引用了InstanceMode,而InstanceMode 引用了City对象,City对象是一个比较简单的对象,只有id、name、中文名称等属性。在工程实际使用环境中,InstanceModel 是比较多的,可能会有数万经常被使用。通过对JVM堆的观察,发现每次InstanceModel对象生成的时候,都会附带出大量的City对象。由于一般应用中City的个数都有限,所以程序中专门对City对象作了缓存。为什么会产生大量City的临时对象呢?
通过在JProfile中查看堆的情况,可以很清楚的看到上述引用关系。这很正常啊?纳闷中,反复查看堆、对象、引用关系等信息,终于发现了线索。那就是,被引用的City对象中只有ID值,没有中文名称等其他信息。这就让我们找到了突破口,通过与开发工程师的交谈,被告知只使用了ID值,没有使用其他的属性。于是我们找到iBatis加载baseView对象的映射文件:
<resultMap id="baseView" class="View">
…
<result column="CITYCODE"
property="InstanceModel.city.id" jdbcType="NUMERIC" />
</resultMap>
注意,在上面这个定义文件中,加载View对象的时候,从同一张数据库表中,加载了View所属的City的代码(id)。但是iBatis框架在达到这个目的的同时,生成了一个临时City对象,并把ID赋了值。这就是原因!!
好了,原因找到了,优化的方案自然就出来了。只需要去掉InstanceModel对City对象的引用,直接取City的ID就行了!再次运行JProfile,发现不再生成大量的City临时对象,优化的目的达到了。
结论:使用Hibernate、iBatis等框架的时候,我们过于热衷于对象引用的方便,忽视了这种方便的代价。有时候,我们只为了使用一个简单类型的数据,却大量加载肥胖对象而不自知。然后抱怨框架效率低,有问题。其实回头看看,良好的数据库设计、良好的对象设计这些基本的东西,是任何框架都不能帮我们做的。同时感叹JProfile这种强大工具给我们带来的好处,让我感觉又回到了DOS Debug 的时代。