When using the tomcat manager app to stop a web app, tomcat discards the classloader for the web app context so that the memory used by the web app can get garbage collected. When the web app uses Hibernate, however, the classloader does not get garbage collected. This means that all classes that it loaded and any static references that these classes may have do not get garbage collected either. This results in memory leakage everytime the web app is stopped with the tomcat manager app.
I've created a simple web app that demonstrates the problem. Note that no Hibernate session or SessionFactory is ever created - it is only necessary to configure hibernate to get the problem to occur.
Prior to narrowing it down to the hibernate configuration process, the code also created a SessionFactory object. When this object is created, cglib is loaded and I did find one static reference to the classloader with the help of a profiler in net.sf.cglib.core.ReflectUtils. I recompiled cglib with a cleanUp() method to null out this reference and called this method from the servlet destroy() method, but the memory leak still occurred. This led to removing the call to buildSessionFactory(). Now with creating the configuration object only, the memory leak will occur. I can't determine what is still pinning the classloader in memory.
If I remove the hibernate code, the classloader is garbage collected properly.
Any insight is appreciated.
-Jay
Hibernate version:
2.1.6
Tomcat version:
4.1.30
hibernate.cfg.xml
Code:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">
<hibernate-configuration>
<session-factory >
<!-- mapping files -->
<mapping resource="z/Foo.xml"/>
</session-factory>
</hibernate-configuration>
Mapping documents:Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping >
<class name="z.Foo" table="FOO">
<id name="id" column="ID" type="long" unsaved-value="null">
<generator class="net.sf.hibernate.id.Assigned"/>
</id>
</class>
</hibernate-mapping>
Code in doGet of Servlet:Code:
SessionFactory sf = null;
try {
Configuration cfg = new Configuration().configure();
// if a SessionFactory is created, cglib.ReflectUtils holds on to the classloader
//sf = new Configuration().configure().buildSessionFactory();
}
catch (HibernateException e) {
throw new ServletException(e);
}
finally {
if (sf != null) {
try {
sf.close();
}
catch (HibernateException e) {
throw new ServletException(e);
}
}
}
PrintWriter out = httpServletResponse.getWriter();
out.write("<html><head></head><body>" +
"Hello World " + new Date() +
"</body></html>");
out.flush();
Code in destroy of Servlet:Code:
// this is needed since Log4J uses java.beans package for configuration and since the classloader is about to be discarded by tomcat
// see the javadoc for java.beans.Introspector for more details
Introspector.flushCaches();