• 2006-08-05

    使用Hibernate开发中,关于内存溢出的问题!

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://cszyj780217.blogbus.com/logs/2985731.html

    内存溢出的错误和连接资源超出的错误,我大致找到了原因:
    程序初始化的时候,要创建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

    收藏到:Del.icio.us




    评论

  • Configuration cfg = new Configuration();
    cfg.configure();
  • Tomcat的内存都一直在增加,并且增加到一定程度,就显示内存溢出。
  • 枫叶飘零
  • 这种实现方法的思路挺可行的。
  • 这种实现方法的思路挺可行的。
    但是我觉得是否有可以在SessionFactory本身,也就是Hibernate本身的解决方法呢?
    今天我在用Hibernate + Struts做了个demo,发现即使只是使用
    Configuration cfg = new Configuration();
    cfg.configure();
    Tomcat的内存都一直在增加,并且增加到一定程度,就显示内存溢出。
    还在寻觅中。。。
    希望前辈指点!