c3p0的helper线程数目过多导致内存溢出

前阵子做得那个系统发布了,结果刚放到服务器上就出了 outOfMemery 错误。 查看发现是由于下的进程导致的:

Daemon Thread [com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#0] (Running)

经过查找,发现在datasource里把c3p0的numHelperThreads设成了1000。正是由于这个线程过多导致了内存溢出,改成10就没有问题了。附上c3p0里对于 numHelperThreads的描述:

numHelperThreads Default: 3 c3p0 is very asynchronous. Slow JDBC operations are generally performed by helper threads that don’t hold contended locks. Spreading these operations over multiple threads can significantly improve performance by allowing multiple operations to be performed simultaneously.

懒惰是我们的天性

在这个项目里已经几天了,现在手头的任务是把现有的sql语句从代码里抽出来,开始同事们的做法是一行一行地从代码里copy,看着长达几十行的sql语句,我会那么勤勤恳恳地把每一行都copy出来吗?这么枯燥的工作可以让程序来完成,懒惰是程序员的天性!!! 写个工具来解析岂不是会很快?说到做到,一个简单的文件读取,一个基于正则表达式的解析器,解析结果输出到一个结果文件里,10分钟搞定!接下来,把这个工具推荐给同事们,结果本来计划2天的工作量一个上午搞定。

又进了一个稍觉郁闷的项目

还没从上一个项目成功的喜悦走中出来,马上又被拉进另一项目了,昨天下午开始接触到这个项目的代码。看到现有代码时的心情只能用一个词来形容:terrible. 丝毫看不出这是一个用面向对象的语言编写的系统,无休止的if else,混乱的类层次结构,蹩脚的变量名,无休止的重复代码,长达百行的大函数,到处充斥着bad smell。。。。 想要做重构几乎没有可能,因为没有test case。

这是个部门以前做的一个项目,比较久远,现在需求变更了,需要对source做调整,然而这么苦难的事情就落在了我的头上。 “在写任何一行代码之前,先要考虑一下后来维护你的代码的人会不会被你的逻辑弄晕掉”我一定坚持这条规则。

罢!罢!罢!做什么不是给公司做,先尽力完成自己的任务吧。

解决了一个死锁问题

此问题中涉及到的对象有Subject , Enclosure , SubjectEnclosure 三个对象, 数据库中对应的三张表为 tbl-subject, tbl-enclosure, tbl-subject-enclosure

Subject和enclosure为一对多关系,在Subject中有个Set<SubjectEnclosure >属性,用于保存隶属于该subject的附件, SubjectEnclosure中有个Subject类型的属性(many to one), Enclosure类型的属性(one to one)此外还有一些其他属性(这也是为什么要做这个中间对象的原因)

问题描述:

在对一个Subject 类型的对象做更新时,如果连续多次更新,偶尔会出现server没有响应的情况,只有把tomcat重启才能恢复正常。

现象分析:

能够导致服务阻塞,重复操作才能偶尔出现,并且把tomcat重启后就能恢复正常,会不会是数据库发生了死锁?用下面的脚本一跑,出现如下结果:

查看死锁的脚本:

SELECT substr(v$lock.sid,1,4) "SID",
       substr(username,1,12) "UserName",
       substr(object-name,1,25) "ObjectName",
       v$lock.type "LockType",
       decode(rtrim(substr(lmode,1,4)),
       '2','Row-S (SS)','3','Row-X (SX)',
       '4','Share',     '5','S/Row-X (SSX)',
       '6','Exclusive', 'Other' ) "LockMode",
       substr(v$session.program,1,25) "ProgramName"
FROM V$LOCK,SYS.DBA-OBJECTS,V$SESSION
WHERE (OBJECT-ID = v$lock.id1
      AND v$lock.sid = v$session.sid
      AND username IS NOT NULL
      AND username NOT IN ('SYS','SYSTEM')
      AND SERIAL# != 1);

结果:

table                      lockType      lockMode
tbl-subject                  TM          ROW-X(SX)行级排他锁,提交前不允许做DML操作
tbl-enclosure                TM          ROW-X(SX)
tbl-subject-enclosure        TM          ROW-X(SX)

再查看一下更新subject时执行的sql语句。

update tbl-subject set ......
update tbl-subject-enclosure set ...
update tbl-enclosure set ...
update tbl-subject-enclosure set ... 
update tbl-enclosure set ...
-------------------如果发生死锁,下面的sql就无法执行----------------------------
update tbl-subject-enclosure set ...

解决方案

因为是 tbl-subject-enclosure这个表被锁住了. 仔细分析sql语句, tbl-enclosure只有在插入subject时需要被插入数据库, 后面对subject的更新操作只需要维护tbl-subject-enclosure这个中间表就可以了, 无需对tbl-enclosure做更新操作,更改SubjectEnclosure中Enclosure类型属性(one to one)的级联type(原来是ALL,改为MERAGE), 再更新Subject,发现此时已经不更新enclosure表了,死锁不再出现了!

Spring, Hibernate, Struts2项目总结

今天做完了第一个正式的公司内部的Spring Hibernate Struts2项目,这个项目与我进入公司后的任何一个项目都不相同,有了很大的收获,值得总结一下。

项目时间:3.5 week

项目人员:

  • teamleader : 1
  • QA : 1
  • 开发,测试人员:4

发现的问题:

这个项目采用的是比较新的技术,因此在构建项目框架,编码过程中,遇到了不少困难,主要集中在以下几个方面:

1,没有很好的OO意识,面向过程编程

2,不愿意重构代码,影响可读性,可测性

3,对持久化框没有大致的了解,对数据持久化的认识仍然停留在 关系数据库的层面上。

4,公共模块变化较多,而且不能及时反应给其他模块开发着。

5,只关注功能实现,忽略了性能调试。

关于以上几个问题我有以下几点建议:

1,在开始编码之前,详细地分析业务需求,然后从需求中抽象出业务对象,充分利用 设计模式 来设计自 己的对象以获得更好的可读性和扩展性。

2, 编码过程中及时地重构自己的代码,提取公用函数,抽取对象,尽量把每个函数的行数控制在十几行的范围内,遵循函数功能单一化的原则。

3,理解Hibernate的原理,采用面向对象的观点去理解数据库。

4,及时地和开发人员沟通,共通模块的性能是整个系统性能提升的关键。

5,尽可能减少代码里的bad smell,用最少的代码做最多的事情,尽量少用循环。

经验:

当然,由于我负责这个系统框架的构建,所以学到的东西也很多。 1,系统采用贫血模型,把对象的行为(service)和状态(pojo)分离,系统结构如下:

  • action
  • service
  • dao
  • pojos
  • utils 系统由上至下依赖,下层的实现对上层隔离(面向接口编程),使得系统的可扩展性大大提高。 2,junit的使用,在此次开发中我采用了TDD开发的方式,当然这只是对我个人而言,我没办法说服别人:-),通过这个项目,我真实体会到了测试驱动开发的威力,在开发后期做性能优化时,只要跑一下test suite,看到了可爱的绿条,我就可以放心地commit source了。无需胆战心惊地去部署到服务器上,然后造数据,跑画面。当然我没有实现case的100%覆盖,而且对action的测试也比较难(目前对我来说)

3,如果你要问我第一次使用TDD方式开发会不会很困难,还是比较容易的,因为我做的主要是公共模块,那些模块比action要好测试些。

4,Hibernate 通过一个玩具项目,一个正式项目,我对Hibernate的认知有了一定的积累,特别是在性能优化方面,有了不少收获。现在总结如下:

  • 尽量使用lazy加载,如果需要eager加载某个associated object,通过detachedCriteria 设置FetchMode.JOIN来连表加载对象,个人觉得比起FetchMode.SELECT性能好些。
  • 对于one-to-many关系,如果关联配置在 one 这一方,建议配置batch-fetch(批量抓取),抓取的size设置在5到10之间比较好。
  • 在获得某个entity的list时,也可以通过对该entity配置batch-fetch 以获取更好的性能。

5,struts2 与struts1相比,struts2让人眼前一亮,更少的侵入性 保证了struts2的action更加容易测试。

关于strut2中的TypeConverter 和 validation 在上一篇文章中已经提及,这里我想再说说struts2的拦截器。拦截器是struts2的精髓,struts2自带的拦截器(在struts-default.xml中)就可以完成大多数的web开发需求,有空我会把struts-default.xml中的拦截器做个介绍。 在使用struts2的过程发现一个action中的函数有被执行两次的情形,原因是使用了struts2标签 的同时调用了 onclick()函数,在这里给大家提个醒,别再在这上面浪费时间:-)

最后还有个大难题: 我们的这个系统使用了c3p0数据库连接池,在系统运行一段时间以后,会出现tomcat假死的情形,具体现象为 页面一直在加载,一直加载不好。我又不知道怎么看tomcat的线程情况,留个脚印,寻找方法中。

Update: 问题解决了,是更新数据库时发生了死锁,解决方法看这里