一次java性能调优总结
我们的系统中新开发了一个数据抽取的功能,东西做完后,一看执行时间那叫一个恼火。参考同类系统同样功能的执行时间,目标:将本地数据处理时间压缩到5秒以内。
第一步:
要想知道哪个地方需要优化,仅凭感觉还是不够,我使用btrace寻找速度慢点原因。下面贴出这次使用的btrace代码:
import static com.sun.btrace.BTraceUtils.name; import static com.sun.btrace.BTraceUtils.print; import static com.sun.btrace.BTraceUtils.println; import static com.sun.btrace.BTraceUtils.probeClass; import static com.sun.btrace.BTraceUtils.probeMethod; import static com.sun.btrace.BTraceUtils.str; import static com.sun.btrace.BTraceUtils.strcat; import static com.sun.btrace.BTraceUtils.timeMillis; import com.sun.btrace.annotations.BTrace; import com.sun.btrace.annotations.Kind; import com.sun.btrace.annotations.Location; import com.sun.btrace.annotations.OnMethod; import com.sun.btrace.annotations.TLS; /** * 监控方法耗时 * */ @BTrace public class PrintTimes { /** * 开始时间 */ @TLS private static long startTime = 0; /** * 方法开始时调用 */ @OnMethod(clazz = "com.xxx.service.XXService", method = "/.+/") public static void startMethod() { startTime = timeMillis(); } /** * 方法结束时调用<br> */ @SuppressWarnings("deprecation") @OnMethod(clazz = "com.xxx.service.XXService", method = "/.+/", location = @Location(Kind.RETURN)) public static void endMethod() { print(strcat(strcat(name(probeClass()), "."), probeMethod())); print(" ["); print(strcat("Time taken : ", str(timeMillis() - startTime))); println("]"); } }
本段代码会匹配 com.xxx.service.XXService 类中的所有方法,打印每个方法的执行时间,每调用一次会打印一次,不是总时间。更多BTrace内容请访问官网:https://kenai.com/projects/btrace
第二步到了这一步已经知道了速度慢集中在哪些点。对于这个系统我可以做哪些工作来优化呢?
1.代码方面调整 a.并行化 (关于线程池线程数量的调整,请参考我的另外一篇关于"设置最优线程数"的文章) b.增加缓存
c.插入操作执行频繁的地方,用原生sql进行批量插入(持久化层使用EclipseLink)
d.避免大数据量表join操作
2.参数调整 a.JVM内存参数 b.数据库连接池参数
3.功能实现设计调整 (最不愿看到的)
根据实际情况采取相应手段调整。
第三步
检测优化后系统该功能性能情况,如果有效则保留,否则恢复代码到本次优化之前的那个状态。
第三步结束后,继续执行第一、二、三步。直到性能达到预期要求。
根据对待优化代码进行分析,最后采用了并行化,增加缓存以及批量插入优化的操作。软件参数和功能设计方面不需要调整。
--------------- 问题纪录 -------------
在将插入操作改成批量插入时,日志报以下错误:
Caused by: org.apache.tomcat.dbcp.dbcp.SQLNestedException: Cannot get a connection, pool error Timeout waiting for idle object 14:45:03.821 [pool-1-thread-2] ERROR java.lang.Throwable - at org.apache.tomcat.dbcp.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:114) 14:45:03.822 [pool-1-thread-2] ERROR java.lang.Throwable - at org.apache.tomcat.dbcp.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044) 14:45:03.822 [pool-1-thread-2] ERROR java.lang.Throwable - at org.eclipse.persistence.sessions.JNDIConnector.connect(JNDIConnector.java:123) 14:45:03.822 [pool-1-thread-2] ERROR java.lang.Throwable - ... 39 more 14:45:03.823 [pool-1-thread-2] ERROR java.lang.Throwable - Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
有几个思路:
1.数据库连接没有成功释放;(得到数据库连接池的监控数据,可以实时看到空闲连接的情况)
2.数据库连接成功释放,但是由于多个线程同时抢占连接而导致有些线程抢不到连接。(这个情况考虑增大数据库连接池)
使用BTrace监控连接池发现只有获得连接没有释放连接操作,看来属于1。由于使用EclipseLink以为 entityManager.close()可以释放连接,经过查阅EclipseLink官网文档,原来是使用不当,下面是获取连接的详细信息,:
JPA 2.0
entityManager.getTransaction().begin(); java.sql.Connection connection = entityManager.unwrap(java.sql.Connection.class); ... entityManager.getTransaction().commit();
最后在finally将connection.close()。