-
2006-08-05
使用Hibernate开发中,关于内存溢出的问题!
内存溢出的错误和连接资源超出的错误,我大致找到了原因:
程序初始化的时候,要创建SessionFactory对象,大家都知道,这是个重量级对象,包含一些初始化信息和预先进行查询的数据。我写了个HibernateUtil类,实现了对Session及其他相关数据操纵的对象封装,也是参照Hibernate官方推荐的那个类改变而写。并写了个Servlet实现创建SessionFactory并存储在InitialContext对象,通过JNDI可以对该对象进行访问。HibernateUtil类中,有个静态块,进行Configuration对象和SessionFactory对象的初始化,每当我类改变时,Tomcat重新reload,这导致了重复执行此代码块,此时前一个
SessionFactory所包含的大量对象还未完全释放,又新创建了个此重量级的对象,从而导致了内存溢出或超出最大的数据连接限制的错误。只是Apache的DBCP和C3P0这两个连接池所反应的错误信息提示使不同,DBCP反映出内存溢出从错误,而C3P0反映出的错误使连接资源耗尽。觉得2个连接池反应的都有道理,从而看出这两个连接池的错误捕捉的出发点是不同。请大家注意了。我优化了下代码,现在不管怎么Tomcat的reload,都没有再出现上述的错误了。
原先的设计思路是把创建SF的工作放到一个Servlet中去,并存储到InitialContext中。当Tomcat启动时候,自动执行此Servlet。那么程序中就可以通过JNDI来得到此SF对象了。
通过对jvm里面Class类对象的跟踪,我发现在Tomcat服务器reload的时候,在ServletContext下产生的对象同处于一个线程内(Servlet本质是就是基于线程的),当reload时候,当前的线程却不能正常关闭!当然也就无法回收该线程内的class对象,这样的话,reload后,就会新产生一个线程,并在该线程内创建SF对象。也就是说,在JVM中,存在了2个SF对象,只是起hashcode不同。我们都知道创建SF这是个很浪费的操作。所以,reload几次后,我指的是间隔比较短,当JVM还没来得及回收无用对象时,又创建了新的SF对象。所以就会容易造成内存溢出或者是连接资源耗尽的错误了。
现在我变通的进行了一些优化,将原来进行SF创建工作的Servlet改变成一个监听器类,本质上Tomcat的reload就是一个ServletContext的开始和结束,那么在结束时,我手动的将SF对象关闭,我是这样实现的。当前运行正常。
监听器:
public class BuildSessionFactory implements ServletContextListener {
private static final long serialVersionUID = 2366489378019950504L;
Logger log = Logger.getLogger(this.getClass());
InitialContext context = null;
static Configuration configuration = null;
static SessionFactory sessionFactory = null;
/*
* (non-Javadoc)
*
* @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
*/
public void contextInitialized(ServletContextEvent arg0) {
// TODO Auto-generated method stub
log.info("初始化HibernateSessionFactory 开始...");
try {
context = new InitialContext();
} catch (NamingException e) {
log.error("初始化上下文对象失败. 详细原因:" + e.getMessage());
}
configuration = new Configuration();
log.info("构建SessionFactory对象.");
sessionFactory = configuration.configure().buildSessionFactory();
if (sessionFactory == null) {
try {
throw new Exception("SessionFactory 创建失败!");
} catch (Exception e) {
log.error("构造SessionFactory对象失败.原因:" + e.getMessage());
}
}
try {
if (context.lookup(Constants.SESSION_FACTORY_JNDI) != null) {
context.rebind(Constants.SESSION_FACTORY_JNDI, sessionFactory);
log.info("SessionFactory 重新绑定到环境中 JNDI 名称:hibernate.");
} else {
context.bind(Constants.SESSION_FACTORY_JNDI, sessionFactory);
log.info("SessionFactory 成功绑定到环境中 JNDI 名称:hibernate.");
}
} catch (NamingException e) {
log.error("进行 JNDI 名字绑定错误. 原因 1 :" + e.getMessage());
} catch (Exception e) {
log.error("进行 JNDI 名字绑定错误. 原因 2 :" + e.getMessage());
} finally {
try {
context.close();
context = null;
} catch (NamingException e) {
e.printStackTrace();
}
}
}
/*
* (non-Javadoc)
*
* @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent)
*/
public void contextDestroyed(ServletContextEvent arg0) {
// TODO Auto-generated method stub
configuration = null;
if (!sessionFactory.isClosed()) {
sessionFactory.close();
}
sessionFactory = null;
}
}
我还不能确保这样就能解决问题。不过,在程序部署后,reload属性要设置成false的。那么也就不会存在这样的问题了。这样也只是方便调试方便而已。
考虑的不成熟,大家谁有更好的建议,thanks -
2006-08-05
Hibernate + Proxool配置
用Hibernate自带的连接池性能不高,而且还存在BUG。因此官方推荐使用c3p0或Proxool连接池。
这里我介绍Hibernate使用Proxool连接池的配置:
首先配置Proxool的配置文件,文件名:Proxool.xml(当然,你也可以用资源文件)
<?xml version="1.0" encoding="UTF-8"?>
<!-- the proxool configuration can be embedded within your own application's.
Anything outside the "proxool" tag is ignored. -->
<something-else-entirely>
<proxool>
<alias>DBPool</alias>
<driver-url>jdbc:mysql://localhost:3306/WebShop</driver-url>
<driver-class>org.gjt.mm.mysql.Driver</driver-class>
<driver-properties>
<property name="user" value="ycoe"/>
<property name="password" value="123456"/></driver-properties>
<maximum-connection-count>10</maximum-connection-count>
<house-keeping-test-sql>select CURRENT_DATE</house-keeping-test-sql>
</proxool>
</something-else-entirely>
这里有几点要说明的
<alias>是连接池的别名,在JDBC中可以用DriverManager.getConnection("Proxool.DBPool");取得一个连接(但你得先在程序运行时先加载它)。
<driver-url>是数据库地址,不用说也明白了。后面也可以带一段参数useUnicode=true&characterEncoding=GB2312这是设定连接的参数,这里是定义了连接使用的编码为GB2312,这是为了解决数据库存取中的乱码问题(如果数据库没有提供编码设定的话,比如MySQL5.0以前版本)
<driver-class>这是JDBC使用的数据库驱动类,对于不同的数据库,有不同的驱动类支持,一般官方会提供
<driver-properties>里面的都很简单啦,不说了。
还有就是<maxmum-connection-count>是设置连接池内生成的最大连接数
Proxool配置完成,把它放在WEB-INF\classes下面(放在哪都没关系,只要让路径Path包含着就行)!
下面是Hibernate的配置:这里我们还是用XML文件,因为它配置映射有独特的昧力
hibernate.cfg.xml
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="hibernate.connection.provider_class">org.hibernate.connection.ProxoolConnectionProvider</property>
<property name="hibernate.proxool.pool_alias">DBPool</property>
<property name="hibernate.proxool.xml">Proxool.xml</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">false</property>
<!-- Drop and re-create the database schema on startup
<property name="hbm2ddl.auto">create</property>
-->
<mapping resource="Orders.hbm.xml"/>
<mapping resource="Users.hbm.xml"/>
</session-factory>
</hibernate-configuration>
Hibernate3版本的配置都使用<property>
首先hibernate.connection.provider_class定义Hibernate的连接加载类,这里Proxool连接池是用这个,不同的连接池有不同的加载类,可以查阅Hibernate文档获取相关信息
hibernate.proxool.pool_alias这里就是用我们上面提到的连接池的别名
hibernate.proxool.xml是向Hibernate声明连接池的配置文件位置,可以用相对或绝对路径,用相对路径时要注意一定在要Path范围内!不然会抛出异常。
dialect是声明SQL语句的方言
show_sql定义是否显示Hibernate生成的SQL语言,一般在调试阶段设为true,完成后再改成false,这样有利于调试。
hbm2ddl.auto是声明是否使用hbm2 ddl工具,也就是根据映射文件生成SQL的DDL文件。
<mapping >这个可是个好东西了,方便的用于映射。如果用资源文件的话就没有这个功能了.呵呵.resource是定义映射文件的位置,和Proxool.xml一样。
不同的连接池用不同的配置,下面提供c3p0连接池在Hibernate中的配置:
c3p0配置
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<property name="hibernate.c3p0.max_size">20</property>
<property name="hibernate.c3p0.min_size">5</property>
<property name="hibernate.c3p0.timeout">120</property>
<property name="hibernate.c3p0.max_statements">100</property>
<property name="hibernate.c3p0.idle_test_period">120</property>
<property name="hibernate.c3p0.acquire_increment">2</property>
好啦,到这里已经把Proxool和Hibernate的配置文件各项说明完了。
还有一个就是映射文件,在这里就不再说明了,挺多内容的,可以出一本书了。
孙卫琴的精通Hibernate里面有很详细的说明
PS:《精通Hibernate》电子工业出版社 这本书里面的东西在Hibernate3里很多都不行的。Hibernate3版本改了不少东西,连包名都改了。
这里想骂孙卫琴几句:拜托,不要一年出一本书行不行啊,花点时间,质量放高一点.太不厚道了,难为我把你出的书都认真了<<Tomcat与java web开发技术详解>><<精通Hibernate:Java对象持久化技术详解>><<精通Struts:基于MVC的Java Web设计与开发>>.对于初学者来说不错,但不够深度,用不着每本都用四五百页来写吧...
唉,要学这些技术,还是得去看看英文版的.
见:
http://ycoe.cnblogs.com/archive/2006/03/20/353677.html -
2006-01-05
扩展 Hibernate 对各类数据源支持
2005 年 12 月 22 日
本文将介绍两种如何在 Hibernate 项目中使用自定义数据源的方法。
Hibernate内嵌了对C3P0,Proxool,JNDI数据源等数据库连接池的支持。但当我们需要使用除了这几个数据源外的其他数据源的时候就有问题了,例如我们需要用Apache的开源连接池项目DBCP,或者说我们想要使用多数JDBC驱动程序中自带的XxxxDataSource时,Hibernate就没有提供对这方面的支持。庆幸的是Hibernate做为一个强大的数据持久层组件,它在实现数据库连接方面的扩展性也是非常强大的。本文将介绍两种如何在Hibernate项目中使用自定义数据源的方法。
本文假设你已经有Hibernate的开发经验。
在开始之前应该先明确你的项目中的具体情况,也就是确认Hibernate内嵌的组件是否真的无法支持你的应用需要。例如C3P0或者Proxool已经可以满足大部分数据库的需要,又或者你的数据源是在应用服务器中配置的,那么你也没有必要进行扩展,你可以直接使用DatasourceConnectionProvider来让Hibernate使用你所定义的数据源。
那么什么时候你需要扩展Hibernate对数据源的支持呢?可能你永远也用不上,但我一直在用。我用的原因可能不能成为正当的理由,因为C3P0或者Proxool总有些小地方的不足让我不爽,个人更偏向于DBCP连接池。或许本文应该改名为《让Hibernate支持DBCP数据源》,其实DBCP只不过是我的一个具体的例子,本文具有更普遍的应用意义。下面我们具体介绍两种不同的扩展思路。
假设我们已经有了一个WEB项目,该项目采用了Struts框架,而且我们已经在Struts中配置了数据源,也有不少的代码是依赖这个数据源运行的。现在我们需要给项目中加入对Hibernate的支持,但又不想去修改旧的已经成功稳定运行的代码了。那我们该怎么办?如果同样在Hibernate配置一个数据源指到同一个数据库,相信你也不乐意这样干,因为一旦配置上有修改那么Struts和Hibernate的配置都需要修改,这个也只是麻烦一点而已,最要命的是没法让原有的代码和Hibernate共用一个数据库连接,因此事务处理也就无从谈起。
说那么多理由,无非就是为了让Hibernate可以使用Struts中配置的数据源,而我们暂且不去考虑这是否是最好的解决方法。
在Hibernate中有一个UserSuppliedConnectionProvider类,其实这个类什么也不干,你一旦让它干点啥吧,它还净出异常,搞得你很是恼火。在Hibernate中,这个类的含义是要求用户自己来提供数据库连接的获取方法,同时当然也要自己负责关闭连接。
为了使用Struts中配置的数据源,我们就不能直接调用SessionFactory.openSession()方法来获取Session实例,因为你如果没有在Hibernate中配置任何的数据库连接,那Hibernate会默认让UserSuppliedConnectionProvider类来跟你捣乱,你会收到很多异常信息,反复提醒我们必须自己提供数据库连接!我们要做还是调用openSession方法,不同的是需要先从Struts的数据源中获取数据库连接,然后传递该连接给openSession方法(参照 SessionFactory.openSession(Connection) 方法)。
下面是我写的一个代码片断
//获取Session实例 public Session getSession(){ ServletContext contxt = .... SessionFactory sessions = .... DataSource ds = (DataSource)context.getAttribute(Globals.DATA_SOURCE_KEY); final Connection conn = ds.getConnection(); return sessions.openSession(conn); } //释放Session public void closeSession(Session ssn){ ssn.connection().close(); ssn.close(); }需要提醒大家注意的是closeSession方法,在该方法中我们必须手工去关闭session对应的数据库连接,我们前面已经提到了,UserSuppliedConnectionProvider类就是要求用户自己提供数据库连接已经连接的关闭。如果没有调用ssn.connection().close()方法,这会导致Struts的数据源的连接没有被释放。
同理,上面提到的Struts只是一个应用普遍的例子,实际中你可以使用任何的外部连接池,你只需要将获取到的数据库连接传递给openSession方法,并自行负责释放数据库连接即可。应该说这是一种最简单的思路,好处是对系统的变动最小,兼容原来的代码。


回页首 Hibernate本身是通过ConnectionProvider接口来实现管理数据库连接的。例如其自带的C3P0ConnectionProvider,ProxoolConnectionProvider等。
在这个思路中,我们希望可以直接在Hibernate的配置文件中配置数据库连接,也就是让Hibernate独揽数据库的管理,真正做到各司其职。为了更了解该接口的使用,你不妨阅读一下Hibernate提供的上面几个类的源码。
接下来我们需要编写一个实现了ConnectionProvider接口的类,要求这个类能支持任何的符合DataSource接口规范的数据源,同时在Hibernate的配置文件中进行参数的设定。首先我们假定我们的类名是DataSourceConnProvider,那我们的配置信息在hibernate.cfg.xml中看起来应该像下面一样
<!-- Connection Pool settings --> <property name="connection.provider_class"> com.liusoft.dlog4j.db.DataSourceConnProvider</property> <property name="dscp.datasource">org.apache.commons.dbcp.BasicDataSource</property> <property name="dscp.driverClassName">sun.jdbc.odbc.JdbcOdbcDriver</property> <property name="dscp.url">jdbc:odbc:dlog4j</property> <property name="dscp.username">admin</property> <property name="dscp.password"></property> <property name="dscp.initialSize">1</property> <property name="dscp.maxActive">200</property> <property name="dscp.maxWait">2000</property> <property name="dscp.defaultAutoCommit">false</property> <property name="dscp.defaultReadOnly">false</property> <property name="dscp.removeAbandoned">true</property> <property name="dscp.removeAbandonedTimeout">120</property> <!-- <property name="dscp.defaultTransactionIsolation">1</property> --> <property name="dscp.poolPreparedStatements">true</property> <property name="dscp.maxOpenPreparedStatements">1000</property>在上面的配置信息中,connection.provider_class是Hibernate本身用来指定不同ConnectionProvider实现类。接下来我们规定了我们的扩展所使用的配置键值都是以dscp.开头,同时我们使用dscp.datasource来指定具体实现了DataSource接口的类名,例如如果使用DBCP这个连接池,那么这个类名应该是org.apache.commons.dbcp.BasicDataSource。对于其他以dscp.开头的且不是dscp.datasource的配置信息都会直接赋值给DataSource的实现类。例如上面的配置中,driverClassName、url、username、password等配置信息都是BasicDataSource类的属性。
下面是我们所实现的DataSourceConnProvider类的源码。
package com.liusoft.dlog4j.db; import java.lang.reflect.Method; import java.sql.Connection; import java.sql.SQLException; import java.util.Iterator; import java.util.Properties; import javax.sql.DataSource; import org.apache.commons.beanutils.BeanUtils; import org.hibernate.HibernateException; import org.hibernate.connection.ConnectionProvider; import com.liusoft.dlog4j.Globals; import com.liusoft.dlog4j.util.StringUtils; /** * 让Hibernate支持各种数据源 * @author Winter Lau */ public class DataSourceConnProvider implements ConnectionProvider { private final static String BASE_KEY = "dscp."; private final static String ENCODING_KEY = "dscp.encoding"; private final static String DATASOURCE_KEY = "dscp.datasource"; protected DataSource dataSource; /* (non-Javadoc) * @see org.hibernate.connection.ConnectionProvider#configure(java.util.Properties) */ public void configure(Properties props) throws HibernateException { String dataSourceClass = null; Properties new_props = new Properties(); Iterator keys = props.keySet().iterator(); while(keys.hasNext()){ String key = (String)keys.next(); if(DATASOURCE_KEY.equalsIgnoreCase(key)){ dataSourceClass = props.getProperty(key); } else if(key.startsWith(BASE_KEY)){ String value = props.getProperty(key); value = StringUtils.replace(value, "{DLOG4J}", Globals.WEBAPP_PATH); new_props.setProperty(key.substring(BASE_KEY.length()), value); } } if(dataSourceClass == null) throw new HibernateException("Property 'dscp.datasource' no defined."); try { dataSource = (DataSource)Class.forName(dataSourceClass).newInstance(); BeanUtils.populate(dataSource, new_props); } catch (Exception e) { throw new HibernateException(e); } } /* (non-Javadoc) * @see org.hibernate.connection.ConnectionProvider#getConnection() */ public Connection getConnection() throws SQLException { final Connection conn = dataSource.getConnection(); if(useProxy && conn!=null){ return (new _Connection(conn,encoding)).getConnection(); } return conn; } /* (non-Javadoc) * @see org.hibernate.connection.ConnectionProvider#closeConnection(java.sql.Connection) */ public void closeConnection(Connection conn) throws SQLException { if(conn!=null && !conn.isClosed()) conn.close(); } /* (non-Javadoc) * @see org.hibernate.connection.ConnectionProvider#close() */ public void close() throws HibernateException { if(dataSource != null) try { Method mClose = dataSource.getClass().getMethod("close",null); mClose.invoke(dataSource, null); } catch (Exception e) { throw new HibernateException(e); } dataSource = null; } /* (non-Javadoc) * @see org.hibernate.connection.ConnectionProvider#supportsAggressiveRelease() */ public boolean supportsAggressiveRelease() { return false; } }在DataSourceConnProvider类中,configure方法会在Hibernate进行初始化的过程中被调用,我们根据配置的DataSource类名创建数据源实例,并将配置参数赋值给该实例后即完成了数据源的初始化。接下来就是实现了getConnection和closeConnection方法分别是获取数据库连接和关闭连接的方法。方法close用来关闭整个数据源,该方法会在Hibernate释放时被调用。
你也可以使用其他一些不同的数据源而不一定非是DBCP数据源。配置完毕后接下来的事情就简单了,直接调用SessionFactory.openSession()方法获取Session实例,直接调用session.close()释放该实例,无需再手工去关闭session所封装的connection接口。
相比较上面两种思路而言,各有千秋。如果你真的有必要扩展Hibernate对数据源的支持,如果你没有兼容旧代码这个问题需要考虑的话,那我更倾向于第二种思路。因为它使得整个项目的各个层次分工非常清晰,而且除了ConnectionProvider 类以外应用的代码也相对简单。
-
2005-12-28
oracle中读写blob字段的问题
通過JDBC操縱Oracle數據庫LOB字段的幾種情況分析縱橫軟件製作中心 雨亦奇2003-6-10 15:14:19
在Oracle中,LOB(Large Object,大型對像)類型的字段現在用得越來越多了。因為這種類型的字段,容量大(最多能容納4GB的數據),且一個表中可以有多個這種類型的字段,很靈活,適用於數據量非常大的業務領域(如圖像、檔案等)。而LONG、LONG RAW等類型的字段,雖然存儲容量也不小(可達2GB),但由於一個表中只能有一個這樣類型的字段的限制,現在已很少使用了。
LOB類型分為BLOB和CLOB兩種:BLOB即二進制大型對像(Binary Large Object),適用於存貯非文本的字節流數據(如程序、圖像、影音等)。而CLOB,即字符型大型對像(Character Large Object),則與字符集相關,適於存貯文本型的數據(如歷史檔案、大部頭著作等)。下面以程序實例說明通過JDBC操縱Oracle數據庫LOB類型字段的幾種情況。
先建立如下兩個測試用的數據庫表,Power Designer PD模型如下:
建表SQL語句為:CREATE TABLE TEST_CLOB ( ID NUMBER(3), CLOBCOL CLOB)
CREATE TABLE TEST_BLOB ( ID NUMBER(3), BLOBCOL BLOB)
一、 CLOB對象的存取
1、往數據庫中插入一個新的CLOB對像
public static void clobInsert(String infile) throws Exception
{
/* 設定不自動提交 */
boolean defaultCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
try {
/* 插入一個空的CLOB對像 */
stmt.executeUpdate("INSERT INTO TEST_CLOB VALUES ('111', EMPTY_CLOB())");
/* 查詢此CLOB對象並鎖定 */
ResultSet rs = stmt.executeQuery("SELECT CLOBCOL FROM TEST_CLOB WHERE ID='111' FOR UPDATE");
while (rs.next()) {
/* 取出此CLOB對像 */
oracle.sql.CLOB clob = (oracle.sql.CLOB)rs.getClob("CLOBCOL");
/* 向CLOB對像中寫入數據 */
BufferedWriter out = new BufferedWriter(clob.getCharacterOutputStream());
BufferedReader in = new BufferedReader(new FileReader(infile));
int c;
while ((c=in.read())!=-1) {
out.write(c);
}
in.close();
out.close();
}
/* 正式提交 */
conn.commit();
} catch (Exception ex) {
/* 出錯回滾 */
conn.rollback();
throw ex;
}
/* 恢復原提交狀態 */
conn.setAutoCommit(defaultCommit);
}
2、修改CLOB對像(是在原CLOB對像基礎上進行覆蓋式的修改)
public static void clobModify(String infile) throws Exception
{
/* 設定不自動提交 */
boolean defaultCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
try {
/* 查詢CLOB對象並鎖定 */
ResultSet rs = stmt.executeQuery("SELECT CLOBCOL FROM TEST_CLOB WHERE ID='111' FOR UPDATE");
while (rs.next()) {
/* 獲取此CLOB對像 */
oracle.sql.CLOB clob = (oracle.sql.CLOB)rs.getClob("CLOBCOL");
/* 進行覆蓋式修改 */
BufferedWriter out = new BufferedWriter(clob.getCharacterOutputStream());
BufferedReader in = new BufferedReader(new FileReader(infile));
int c;
while ((c=in.read())!=-1) {
out.write(c);
}
in.close();
out.close();
}
/* 正式提交 */
conn.commit();
} catch (Exception ex) {
/* 出錯回滾 */
conn.rollback();
throw ex;
}
/* 恢復原提交狀態 */
conn.setAutoCommit(defaultCommit);
}
3、替換CLOB對像(將原CLOB對像清除,換成一個全新的CLOB對像)
public static void clobReplace(String infile) throws Exception
{
/* 設定不自動提交 */
boolean defaultCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
try {
/* 清空原CLOB對像 */
stmt.executeUpdate("UPDATE TEST_CLOB SET CLOBCOL=EMPTY_CLOB() WHERE ID='111'");
/* 查詢CLOB對象並鎖定 */
ResultSet rs = stmt.executeQuery("SELECT CLOBCOL FROM TEST_CLOB WHERE ID='111' FOR UPDATE");
while (rs.next()) {
/* 獲取此CLOB對像 */
oracle.sql.CLOB clob = (oracle.sql.CLOB)rs.getClob("CLOBCOL");
/* 更新數據 */
BufferedWriter out = new BufferedWriter(clob.getCharacterOutputStream());
BufferedReader in = new BufferedReader(new FileReader(infile));
int c;
while ((c=in.read())!=-1) {
out.write(c);
}
in.close();
out.close();
}
/* 正式提交 */
conn.commit();
} catch (Exception ex) {
/* 出錯回滾 */
conn.rollback();
throw ex;
}
/* 恢復原提交狀態 */
conn.setAutoCommit(defaultCommit);
}
4、CLOB對像讀取
public static void clobRead(String outfile) throws Exception
{
/* 設定不自動提交 */
boolean defaultCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
try {
/* 查詢CLOB對像 */
ResultSet rs = stmt.executeQuery("SELECT * FROM TEST_CLOB WHERE ID='111'");
while (rs.next()) {
/* 獲取CLOB對像 */
oracle.sql.CLOB clob = (oracle.sql.CLOB)rs.getClob("CLOBCOL");
/* 以字符形式輸出 */
BufferedReader in = new BufferedReader(clob.getCharacterStream());
BufferedWriter out = new BufferedWriter(new FileWriter(outfile));
int c;
while ((c=in.read())!=-1) {
out.write(c);
}
out.close();
in.close();
}
} catch (Exception ex) {
conn.rollback();
throw ex;
}
/* 恢復原提交狀態 */
conn.setAutoCommit(defaultCommit);
}
二、 BLOB對象的存取
1、 向數據庫中插入一個新的BLOB對像
public static void blobInsert(String infile) throws Exception
{
/* 設定不自動提交 */
boolean defaultCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
try {
/* 插入一個空的BLOB對像 */
stmt.executeUpdate("INSERT INTO TEST_BLOB VALUES ('222', EMPTY_BLOB())");
/* 查詢此BLOB對象並鎖定 */
ResultSet rs = stmt.executeQuery("SELECT BLOBCOL FROM TEST_BLOB WHERE ID='222' FOR UPDATE");
while (rs.next()) {
/* 取出此BLOB對像 */
oracle.sql.BLOB blob = (oracle.sql.BLOB)rs.getBlob("BLOBCOL");
/* 向BLOB對像中寫入數據 */
BufferedOutputStream out = new BufferedOutputStream(blob.getBinaryOutputStream());
BufferedInputStream in = new BufferedInputStream(new FileInputStream(infile));
int c;
while ((c=in.read())!=-1) {
out.write(c);
}
in.close();
out.close();
}
/* 正式提交 */
conn.commit();
} catch (Exception ex) {
/* 出錯回滾 */
conn.rollback();
throw ex;
}
/* 恢復原提交狀態 */
conn.setAutoCommit(defaultCommit);
}
2、修改BLOB對像(是在原BLOB對像基礎上進行覆蓋式的修改)
public static void blobModify(String infile) throws Exception
{
/* 設定不自動提交 */
boolean defaultCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
try {
/* 查詢BLOB對象並鎖定 */
ResultSet rs = stmt.executeQuery("SELECT BLOBCOL FROM TEST_BLOB WHERE ID='222' FOR UPDATE");
while (rs.next()) {
/* 取出此BLOB對像 */
oracle.sql.BLOB blob = (oracle.sql.BLOB)rs.getBlob("BLOBCOL");
/* 向BLOB對像中寫入數據 */
BufferedOutputStream out = new BufferedOutputStream(blob.getBinaryOutputStream());
BufferedInputStream in = new BufferedInputStream(new FileInputStream(infile));
int c;
while ((c=in.read())!=-1) {
out.write(c);
}
in.close();
out.close();
}
/* 正式提交 */
conn.commit();
} catch (Exception ex) {
/* 出錯回滾 */
conn.rollback();
throw ex;
}
/* 恢復原提交狀態 */
conn.setAutoCommit(defaultCommit);
}
3、替換BLOB對像(將原BLOB對像清除,換成一個全新的BLOB對像)
public static void blobReplace(String infile) throws Exception
{
/* 設定不自動提交 */
boolean defaultCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
try {
/* 清空原BLOB對像 */
stmt.executeUpdate("UPDATE TEST_BLOB SET BLOBCOL=EMPTY_BLOB() WHERE ID='222'");
/* 查詢此BLOB對象並鎖定 */
ResultSet rs = stmt.executeQuery("SELECT BLOBCOL FROM TEST_BLOB WHERE ID='222' FOR UPDATE");
while (rs.next()) {
/* 取出此BLOB對像 */
oracle.sql.BLOB blob = (oracle.sql.BLOB)rs.getBlob("BLOBCOL");
/* 向BLOB對像中寫入數據 */
BufferedOutputStream out = new BufferedOutputStream(blob.getBinaryOutputStream());
BufferedInputStream in = new BufferedInputStream(new FileInputStream(infile));
int c;
while ((c=in.read())!=-1) {
out.write(c);
}
in.close();
out.close();
}
/* 正式提交 */
conn.commit();
} catch (Exception ex) {
/* 出錯回滾 */
conn.rollback();
throw ex;
}
/* 恢復原提交狀態 */
conn.setAutoCommit(defaultCommit);
}
4、BLOB對像讀取
public static void blobRead(String outfile) throws Exception
{
/* 設定不自動提交 */
boolean defaultCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
try {
/* 查詢BLOB對像 */
ResultSet rs = stmt.executeQuery("SELECT BLOBCOL FROM TEST_BLOB WHERE ID='222'");
while (rs.next()) {
/* 取出此BLOB對像 */
oracle.sql.BLOB blob = (oracle.sql.BLOB)rs.getBlob("BLOBCOL");
/* 以二進制形式輸出 */
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(outfile));
BufferedInputStream in = new BufferedInputStream(blob.getBinaryStream());
int c;
while ((c=in.read())!=-1) {
out.write(c);
}
in.close();
out.close();
}
/* 正式提交 */
conn.commit();
} catch (Exception ex) {
/* 出錯回滾 */
conn.rollback();
throw ex;
}
/* 恢復原提交狀態 */
conn.setAutoCommit(defaultCommit);
}
觀察上述程序對LOB類型字段的存取,我們可以看出,較之其它類型字段,有下面幾個顯著不同的特點:
一是必須取消自動提交。
存取操作開始前,必須用setAutoCommit(false)取消自動提交。其它類型字段則無此特殊要求。這是因為存取LOB類型字段時,通常要進行多次操作可以完成。不這樣的話,Oracle將拋出「讀取違反順序」的錯誤。
二是插入方式不同。
LOB數據不能像其它類型數據一樣直接插入(INSERT)。插入前必須先插入一個空的LOB對象,CLOB類型的空對像為EMPTY_CLOB(),BLOB類型的空對像為EMPTY_BLOB()。之後通過SELECT命令查詢得到先前插入的記錄並鎖定,繼而將空對像修改為所要插入的LOB對象。
三是修改方式不同。
其它類型的字段修改時,用UPDATE … SET…命令即可。而LOB類型字段,則只能用SELECT … FOR UPDATE命令將記錄查詢出來並鎖定,然後才能修改。且修改也有兩種改法:一是在原數據基礎上的修改(即覆蓋式修改),執行SELECT … FOR UPDATE後再改數據;二是替換(先將原數據清掉,再修改),先執行UPDATE命令將LOB字段之值設為空的LOB對象,然後進行第一種改法。建議使用替換的方法,以實現與其它字段UPDATE操作後一樣的效果。
四是存取時應使用由數據庫JDBC驅動程序提供的LOB操作類。
對於Oracle數據庫,應使用oracle.sql.CLOB和oracle.sql.BLOB。不使用由數據庫JDBC驅動程序提供的LOB類時,程序運行時易於出現「抽像方法調用」的錯誤,這是因為JDBC所定義的java.sql.Clob與java.sql.Blob接口,其中的一些方法並未在數據庫廠家提供的驅動程序中真正實現。
五是存取手段與文件操作相仿。
對於BLOB類型,應用InputStream/OutputStream類,此類不進行編碼轉換,逐個字節存取。oracle.sql.BLOB類相應提供了getBinaryStream()和getBinaryOutputStream()兩個方法,前一個方法用於讀取Oracle的BLOB字段,後一個方法用於將數據寫入Oracle的BLOB字段。
對於CLOB類型,應用Reader/Writer類,此類進行編碼轉換。oracle.sql.CLOB類相應提供了getCharacterStream()和getCharacterOutputStream()兩個方法,前一個方法用於讀取Oracle的CLOB字段,後一個方法用於將數據寫入Oracle的CLOB字段。
需要說明的是,為了大幅提高程序執行效率,對BLOB/CLOB字段的讀寫操作,應該使用緩衝操作類(帶Buffered前綴),即:BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。例程中全部使用了緩衝操作類。
小結:通過JDBC操縱Oracle數據庫的LOB字段,不外乎插入、修改、替換、讀取四種方式,掌握起來並不難。在實際操作中要注意上面所說的幾點,結合閱讀例程源程序,用戶會很快明白LOB類型字段的使用的,也必將領悟到這種類型字段的妙處!源文件下載>>
(網頁編輯:編程浪子) -
2005-09-22
JVM的垃圾回收机制详解和调优
1.JVM的gc概述
gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存。java语言并不要求jvm有gc,也没有规定gc如何工作。不过常用的jvm都有gc,而且大多数gc都使用类似的算法管理内存和执行收集操作。
在充分理解了垃圾收集算法和执行过程后,才能有效的优化它的性能。有些垃圾收集专用于特殊的应用程序。比如,实时应用程序主要是为了避免垃圾收集中断,而大多数OLTP应用程序则注重整体效率。理解了应用程序的工作负荷和jvm支持的垃圾收集算法,便可以进行优化配置垃圾收集器。
垃圾收集的目的在于清除不再使用的对象。gc通过确定对象是否被活动对象引用来确定是否收集该对象。gc首先要判断该对象是否是时候可以收集。两种常用的方法是引用计数和对象引用遍历。
1.1.引用计数
引用计数存储对特定对象的所有引用数,也就是说,当应用程序创建引用以及引用超出范围时,jvm必须适当增减引用数。当某对象的引用数为0时,便可以进行垃圾收集。
1.2.对象引用遍历
早期的jvm使用引用计数,现在大多数jvm采用对象引用遍历。对象引用遍历从一组对象开始,沿着整个对象图上的每条链接,递归确定可到达(reachable)的对象。如果某对象不能从这些根对象的一个(至少一个)到达,则将它作为垃圾收集。在对象遍历阶段,gc必须记住哪些对象可以到达,以便删除不可到达的对象,这称为标记(marking)对象。
下一步,gc要删除不可到达的对象。删除时,有些gc只是简单的扫描堆栈,删除未标记的未标记的对象,并释放它们的内存以生成新的对象,这叫做清除(sweeping)。这种方法的问题在于内存会分成好多小段,而它们不足以用于新的对象,但是组合起来却很大。因此,许多gc可以重新组织内存中的对象,并进行压缩(compact),形成可利用的空间。
为此,gc需要停止其他的活动活动。这种方法意味着所有与应用程序相关的工作停止,只有gc运行。结果,在响应期间增减了许多混杂请求。另外,更复杂的gc不断增加或同时运行以减少或者清除应用程序的中断。有的gc使用单线程完成这项工作,有的则采用多线程以增加效率。
2.几种垃圾回收机制
2.1.标记-清除收集器
这种收集器首先遍历对象图并标记可到达的对象,然后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器一般使用单线程工作并停止其他操作。
2.2.标记-压缩收集器
有时也叫标记-清除-压缩收集器,与标记-清除收集器有相同的标记阶段。在第二阶段,则把标记对象复制到堆栈的新域中以便压缩堆栈。这种收集器也停止其他操作。
2.3.复制收集器
这种收集器将堆栈分为两个域,常称为半空间。每次仅使用一半的空间,jvm生成的新对象则放在另一半空间中。gc运行时,它把可到达对象复制到另一半空间,从而压缩了堆栈。这种方法适用于短生存期的对象,持续复制长生存期的对象则导致效率降低。
2.4.增量收集器
增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾。这会造成较小的应用程序中断。
2.5.分代收集器
这种收集器把堆栈分为两个或多个域,用以存放不同寿命的对象。jvm生成的新对象一般放在其中的某个域中。过一段时间,继续存在的对象将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优化性能。
2.6.并发收集器
并发收集器与应用程序同时运行。这些收集器在某点上(比如压缩时)一般都不得不停止其他操作以完成特定的任务,但是因为其他应用程序可进行其他的后台操作,所以中断其他处理的实际时间大大降低。
2.7.并行收集器
并行收集器使用某种传统的算法并使用多线程并行的执行它们的工作。在多cpu机器上使用多线程技术可以显著的提高java应用程序的可扩展性。
3.Sun HotSpot 1.4.1 JVM堆大小的调整
Sun HotSpot 1.4.1使用分代收集器,它把堆分为三个主要的域:新域、旧域以及永久域。Jvm生成的所有新对象放在新域中。一旦对象经历了一定数量的垃圾收集循环后,便获得使用期并进入旧域。在永久域中jvm则存储class和method对象。就配置而言,永久域是一个独立域并且不认为是堆的一部分。
下面介绍如何控制这些域的大小。可使用-Xms和-Xmx 控制整个堆的原始大小或最大值。
下面的命令是把初始大小设置为128M:
java –Xms128m
–Xmx256m为控制新域的大小,可使用-XX:NewRatio设置新域在堆中所占的比例。
下面的命令把整个堆设置成128m,新域比率设置成3,即新域与旧域比例为1:3,新域为堆的1/4或32M:
java –Xms128m –Xmx128m
–XX:NewRatio =3可使用-XX:NewSize和-XX:MaxNewsize设置新域的初始值和最大值。
下面的命令把新域的初始值和最大值设置成64m:
java –Xms256m –Xmx256m –Xmn64m
永久域默认大小为4m。运行程序时,jvm会调整永久域的大小以满足需要。每次调整时,jvm会对堆进行一次完全的垃圾收集。
使用-XX:MaxPerSize标志来增加永久域搭大小。在WebLogic Server应用程序加载较多类时,经常需要增加永久域的最大值。当jvm加载类时,永久域中的对象急剧增加,从而使jvm不断调整永久域大小。为了避免调整,可使用-XX:PerSize标志设置初始值。
下面把永久域初始值设置成32m,最大值设置成64m。
java -Xms512m -Xmx512m -Xmn128m -XX:PermSize=32m -XX:MaxPermSize=64m
默认状态下,HotSpot在新域中使用复制收集器。该域一般分为三个部分。第一部分为Eden,用于生成新的对象。另两部分称为救助空间,当Eden充满时,收集器停止应用程序,把所有可到达对象复制到当前的from救助空间,一旦当前的from救助空间充满,收集器则把可到达对象复制到当前的to救助空间。From和to救助空间互换角色。维持活动的对象将在救助空间不断复制,直到它们获得使用期并转入旧域。使用-XX:SurvivorRatio可控制新域子空间的大小。
同NewRation一样,SurvivorRation规定某救助域与Eden空间的比值。比如,以下命令把新域设置成64m,Eden占32m,每个救助域各占16m:
java -Xms256m -Xmx256m -Xmn64m -XX:SurvivorRation =2
如前所述,默认状态下HotSpot对新域使用复制收集器,对旧域使用标记-清除-压缩收集器。在新域中使用复制收集器有很多意义,因为应用程序生成的大部分对象是短寿命的。理想状态下,所有过渡对象在移出Eden空间时将被收集。如果能够这样的话,并且移出Eden空间的对象是长寿命的,那么理论上可以立即把它们移进旧域,避免在救助空间反复复制。但是,应用程序不能适合这种理想状态,因为它们有一小部分中长寿命的对象。最好是保持这些中长寿命的对象并放在新域中,因为复制小部分的对象总比压缩旧域廉价。为控制新域中对象的复制,可用-XX:TargetSurvivorRatio控制救助空间的比例(该值是设置救助空间的使用比例。如救助空间位1M,该值50表示可用500K)。该值是一个百分比,默认值是50。当较大的堆栈使用较低的sruvivorratio时,应增加该值到80至90,以更好利用救助空间。用-XX:maxtenuring threshold可控制上限。
为放置所有的复制全部发生以及希望对象从eden扩展到旧域,可以把MaxTenuring Threshold设置成0。设置完成后,实际上就不再使用救助空间了,因此应把SurvivorRatio设成最大值以最大化Eden空间,设置如下:
java … -XX:MaxTenuringThreshold=0 –XX:SurvivorRatio=50000 …
4.BEA JRockit JVM的使用
Bea WebLogic 8.1使用的新的JVM用于Intel平台。在Bea安装完毕的目录下可以看到有一个类似于jrockit81sp1_141_03的文件夹。这就是Bea新JVM所在目录。不同于HotSpot把Java字节码编译成本地码,它预先编译成类。JRockit还提供了更细致的功能用以观察JVM的运行状态,主要是独立的GUI控制台(只能适用于使用Jrockit才能使用jrockit81sp1_141_03自带的console监控一些cpu及memory参数)或者WebLogic Server控制台。
Bea JRockit JVM支持4种垃圾收集器:
4.1.1.分代复制收集器
它与默认的分代收集器工作策略类似。对象在新域中分配,即JRockit文档中的nursery。这种收集器最适合单cpu机上小型堆操作。
4.1.2.单空间并发收集器
该收集器使用完整堆,并与背景线程共同工作。尽管这种收集器可以消除中断,但是收集器需花费较长的时间寻找死对象,而且处理应用程序时收集器经常运行。如果处理器不能应付应用程序产生的垃圾,它会中断应用程序并关闭收集。
分代并发收集器 这种收集器在护理域使用排它复制收集器,在旧域中则使用并发收集器。由于它比单空间共同发生收集器中断频繁,因此它需要较少的内存,应用程序的运行效率也较高,注意,过小的护理域可以导致大量的临时对象被扩展到旧域中。这会造成收集器超负荷运作,甚至采用排它性工作方式完成收集。
4.1.3.并行收集器
该收集器也停止其他进程的工作,但使用多线程以加速收集进程。尽管它比其他的收集器易于引起长时间的中断,但一般能更好的利用内存,程序效率也较高。
默认状态下,JRockit使用分代并发收集器。要改变收集器,可使用-Xgc:<gc_name>,对应四个收集器分别为gencopy,singlecon,gencon以及parallel。可使用-Xms和-Xmx设置堆的初始大小和最大值。要设置护理域,则使用-Xns:java –jrockit –Xms512m –Xmx512m –Xgc:gencon –Xns128m…尽管JRockit支持-verbose:gc开关,但它输出的信息会因收集器的不同而异。JRockit还支持memory、load和codegen的输出。
注意 :如果 使用JRockit JVM的话还可以使用WLS自带的console(C:\bea\jrockit81sp1_141_03\bin下)来监控一些数据,如cpu,memery等。要想能构监控必须在启动服务时startWeblogic.cmd中加入-Xmanagement参数。
5.如何从JVM中获取信息来进行调整
-verbose.gc开关可显示gc的操作内容。打开它,可以显示最忙和最空闲收集行为发生的时间、收集前后的内存大小、收集需要的时间等。打开-xx:+ printgcdetails开关,可以详细了解gc中的变化。打开-XX: + PrintGCTimeStamps开关,可以了解这些垃圾收集发生的时间,自jvm启动以后以秒计量。最后,通过-xx: + PrintHeapAtGC开关了解堆的更详细的信息。为了了解新域的情况,可以通过-XX:=PrintTenuringDistribution开关了解获得使用期的对象权。
6.Pdm系统JVM调整
6.1.服务器:前提内存1G 单CPU
可通过如下参数进行调整:-server 启用服务器模式(如果CPU多,服务器机建议使用此项)
-Xms,-Xmx一般设为同样大小。 800m
-Xmn 是将NewSize与MaxNewSize设为一致。320m
-XX:PerSize 64m
-XX:NewSize 320m 此值设大可调大新对象区,减少Full GC次数
-XX:MaxNewSize 320m
-XX:NewRato NewSize设了可不设。4
-XX: SurvivorRatio 4
-XX:userParNewGC 可用来设置并行收集
-XX:ParallelGCThreads 可用来增加并行度 4
-XXUseParallelGC 设置后可以使用并行清除收集器
-XX:UseAdaptiveSizePolicy 与上面一个联合使用效果更好,利用它可以自动优化新域大小以及救助空间比值
6.2.客户机:通过在JNLP文件中设置参数来调整客户端JVM
JNLP中参数:initial-heap-size和max-heap-size
这可以在framework的RequestManager中生成JNLP文件时加入上述参数,但是这些值是要求根据客户机的硬件状态变化的(如客户机的内存大小等)。建议这两个参数值设为客户机可用内存的60%(有待测试)。为了在动态生成JNLP时以上两个参数值能够随客户机不同而不同,可靠虑获得客户机系统信息并将这些嵌到首页index.jsp中作为连接请求的参数。
在设置了上述参数后可以通过Visualgc 来观察垃圾回收的一些参数状态,再做相应的调整来改善性能。一般的标准是减少fullgc的次数,最好硬件支持使用并行垃圾回收(要求多CPU)。
-
2005-08-18
google使用指南
[B]1,前言[/B]
我是在2000年上半年知道Google的。在这之前,我搜索英文信息通常用AltaVista,而搜索中文信息则常用Sina。但自使用了Google之后,它便成为我的Favorite Search engine了。这也得感谢新浪网友曹溪,因为当初正是因为他的大力推介,才使我识得了Google。
记得1996年夏季的时候,当我第一次接触Internet,便被扑面而来的魔力征服了。那种天涯咫尺的感觉,真是妙不可言。在经历了疯狂的WWW冲浪和如痴如醉的BBS沉迷之后,我意识到Internet对我影响至深的还是在于学习方式的变迁。
如何来描述这种变迁呢?以前的学习,一般需要预先在肚子里存储下足够的知识,必要时,就从海量的信息中提取所需的部分。这种学习方式造就了很多“才高八斗,学富五车”的大才子。但是,到了信息领域大大超出“四书五经”的新时期,预先无目的的吞下海量信息的学习方式就有些不合时宜了。比方说,我们到了大型的图书城,往... -
2005-06-23
javascript函数库
校验浮点型最大值: checkDoubleMaxValue(str,val)
校验浮点型是否为非负数: isNotNegativeDouble(str)
校验字符串是否为日期型: checkIsValidDate(str)
校验两个日期的先后: checkDateEarlier(strStart,strEnd)
校验字符串是否为email型: checkEmail(str)校验字符串是否为中文: checkIsChinese(str)
计算字符串的长度,一个汉字两个字符: realLength()
校验字符串是否符合自定义正则表达式: checkMask(str,pat)
得到文件的后缀名: getFilePostfix(oFile)
-------------- 函数检索 --------------
*//**
* added by LxcJie 2004.6.25
* 去除多余空格函数
* ...








