开发包括使用他人的和写自己的。在使用他人的,网络确实提供了很大的方便,可以很快地抄到很有用的语句。但是对于开发server端,我的建议是使用网络search作为速成,但是要开发上线系统的时候,最好到官网上看一下人家是怎么说的。
项目(web app)需要对所有使用资源进行关闭。tomcat是web container,web项目作为web app运行在web容器中,而不是作为进程独立运行的。deploy web app和关闭进程不一样,不会确保资源的释放和线程的全部结束,必须要写destory的。初学的开发人员很容易忽略,因为使用Eclipse开发中,开发调测都是重启tomcat的,相当于关闭了tomcat的进程。但是你不清楚生产环境的情况,生产环境中部署的可能并不只你一个项目,部署人员可能是通过deploy,而不是关闭tomcat再重启。
虽然一直没有使用过C3P0,但是在项目退出时必须要关闭的。C3P0维护的是数据库连接池,这些连接还要确保长时间没有sql处理,通过心跳维持确保连接不断开,可以判断必须要关闭,这也是在官网中Quick start里明确提到的。不关闭的结果是数据库的连接不断被占,直至占满,除非关闭tomcat。
如何确保deploy时释放所有资源,很简单,看tomcat的log。tomcat是有检查的,如果出现线程没有释放或者对象不能释放,在给出告警的,可以在catalina.out中查看。在Eclipse的console中也可以看到(不要关闭tomcat,否者会终止tomcat和console的联系,不在console中显示)。例如:
01-Jul-2016 12:00:10.360 WARNING [localhost-startStop-3]
org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads
The web application [xxxx-cap] appears to have started a thread named [Abandoned connection cleanup thread]
but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
java.lang.Object.wait(Native Method)
java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
com.mysql.jdbc.AbandonedConnectionCleanupThread.run(AbandonedConnectionCleanupThread.java:43)
必须确保在deploy的时候,tomcat不报错,除非你能证明这个错100%不会导致内存泄漏。出现这种情况,通常就是在退出时没有正确关闭和释放所有的资源。下面是我写的一个使用C3P0的Mysql管理器。
public class C3p0MysqlManager {
private static Logger logger = LogManager.getLogger(); //在上线项目中尽量不要使用System.out.println。
private static final Map<String,ComboPooledDataSource> dataSourceDb = new HashMap<>();
/**
* 在管理器中加入某个数据源
* @param name 数据源的名字
* @param jdbcUrl
* @param user
* @param pw
* @param minPoolSize
* @param maxPoolSize
* @param poolIncrement
* @throws Exception
*/
public static void open (String name , String jdbcUrl,String user, String pw,
int minPoolSize,int maxPoolSize,int poolIncrement) throws Exception{
if(dataSourceDb.containsKey(name))
throw new Exception("DataSource " + name + " already exist, please close first.");
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl(jdbcUrl);
dataSource.setUser(user);
dataSource.setPassword(pw);
dataSource.setMinPoolSize(minPoolSize);
dataSource.setAcquireIncrement(poolIncrement);
dataSource.setMaxPoolSize(maxPoolSize);
dataSource.setMaxIdleTime(minPoolSize);
dataSource.setInitialPoolSize(minPoolSize);
/* 检查数据源是否有效。C3P0不会在创建dataSource时就去连接,在第一次连接请求时进行。
* 一方面会增加第一个用户请求的处理时间,影响第一个用户的体验;
* 另一方面如果密码错误或者地址不可访问,我们需要在项目部署的第一时间知道是否部署正确,而不是等到业务来了才发现。*/
try{
Connection conn = dataSource.getConnection();
conn.close();
}catch(Exception e){
DataSources.destroy( dataSource); // or dataSource.close()
throw new Exception("DataSource " + name + " open error :" + e.toString());
}
dataSourceDb.put(name, dataSource);
logger.info("Open DataSource '" + name + "', current connections number is " + dataSource.getNumConnections());
}
/**
* 关闭某个数据源
* @param name 数据源名字
*/
public static void close(String name){
ComboPooledDataSource dataSource = dataSourceDb.remove(name);
if(dataSource != null)
dataSource.close();
}
/**
* 在项目结束的时候,应释放所有资源,应调用本方法。建议阅读C3P0官网。
*/
public static void destroy(){
Iterator<Map.Entry<String,ComboPooledDataSource>> iter = dataSourceDb.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String,ComboPooledDataSource> entry = iter.next();
ComboPooledDataSource dataSource = (ComboPooledDataSource)entry.getValue();
dataSource.close();
}
dataSourceDb.clear();
try {
com.mysql.cj.jdbc.AbandonedConnectionCleanupThread.shutdown();
} catch (InterruptedException e) {
logger.error(e);
}
Enumeration<java.sql.Driver> drivers = DriverManager.getDrivers();
while(drivers.hasMoreElements()) {
try {
Driver driver = drivers.nextElement();
DriverManager.deregisterDriver(driver);
} catch (SQLException e) {
logger.error("C3M0Manager destroy error : " + e.toString());
}
}
}
/**
* @param name 数据源的名字
* @return 获取某个数据源的连接
*/
public static Connection getConnection(String name){
try {
ComboPooledDataSource dataSource = dataSourceDb.get(name);
return dataSource == null ? null : dataSource.getConnection();
} catch (SQLException e) {
logger.error(e);
return null;
}
}
}