[ http://jira.andromda.org/browse/BPM-293?page=comments#action_13700 ]
Wouter Zoons commented on BPM-293: ---------------------------------- thanks for investigating this .. I must say I've seen other explanations too on the JBoss and Tomcat forums about why stuff fails after a certain number of redeploys, not sure it's the same thing though would it be possible to provide plain CVS patches for the code you posted, I will have a hard time copying & pasting everything and figuring out what goes where exactly (I don't want to make any mistakes with this thing) anyway, I will discuss this with the other AndroMDA developers before applying it > Class memory allocation not released after undeployment. > -------------------------------------------------------- > > Key: BPM-293 > URL: http://jira.andromda.org/browse/BPM-293 > Project: Bpm4Struts Cartridge > Type: Bug > Versions: 3.2 > Environment: SNAPSHOT > Reporter: Pierre Colot > Assignee: Wouter Zoons > Priority: Blocker > > My CRUD application is based on Manageable entity + Spring + Hibernate + > MySQL. This application is deployed on Jboss 4.0.4GA or Tomcat 5.5.17. > My CRUD based application doesn't release the memory allocated to the > application specifc, spring, hibernate and cglib classes. > The problem occurs on both environments under Windows. This problem occurs > violently with Sun SDK (PermGen error) and more silently with BEA JRockit SDK > (Heap error). > This problem occurs after 6-10 Deploy/Undeploy cycles with small heap size. > I partially track down the problem : > - Enumeration Application classes contain cyclic references.Those cycle have > to be broken at undeployment time > - Spring framework requires to explicitely release resources at undeployment > time. This is currently not done. > - Hibernate also require to explicitely release resource at undeployment > time. This is currently not done. > - Cglib depends of Spring resource release, without Spring release, no Cglib > release > Even after those closures, all resources are not recovered : > - all enumeration application classes are recoverd by the GC. > - 90 % of Spring+Hibernate+Cglib class have been recovered by the GC. > - Application independant classes, kernel of Spring and Hibernate, are not > recovered by GC. Those infrastructure classes are reused by the next > deployment cycle. > - Few application class remain in memory and generate a small memory leak. > If anyone is interested to continue this cleaning : > Enumeration patch : > [code] > --- > R:\andromda-src-3.2-SNAPSHOT-20061001\cartridges\andromda-java\src\templates\java\Enumeration.vsl > Mon Oct 02 11:38:39 2006 > +++ > R:\andromda-src-3.2-SNAPSHOT-20061001-fork\cartridges\andromda-java\src\templates\java\Enumeration.vsl > Thu Oct 05 15:49:51 2006 > @@ -43,7 +43,7 @@ > /** > $literal.getDocumentation(" * ") > */ > - public static final $enumeration.name $literal.name = new > ${enumeration.name}($literal.enumerationValue); > + public static $enumeration.name $literal.name = new > ${enumeration.name}($literal.enumerationValue); > > #end > private $enumeration.literalType.fullyQualifiedName value; > @@ -119,7 +119,7 @@ > */ > public static java.util.List${literalsTemplateType} literals() > { > - return literals; > + return java.util.Collections.unmodifiableList(literals); > } > > /** > @@ -131,7 +131,23 @@ > */ > public static java.util.List${namesTemplateType} names() > { > - return names; > + return java.util.Collections.unmodifiableList(names); > + } > + > + /** > + * This method HAS TO BE CALLED during undeployment. This method > + * releases collection instances containing cyclic references > + * for allowing GC during undeployment. Cyclic reference occurs directly > + * or thru indirect Collection usage. > + */ > + public static void clear() > + { > +#foreach ($literal in $enumeration.literals) > + $literal.name = null; > +#end > + if (values != null) values.clear(); > + if (literals != null) literals.clear(); > + if (names != null) names.clear(); > } > > /** > @@ -198,7 +214,5 @@ > literals.add($value); > names.add("$literal.name"); > #end > - literals = java.util.Collections.unmodifiableList(literals); > - names = java.util.Collections.unmodifiableList(names); > } > } > [/code] > and call clear() from your servlet undeploy listener > Hibernate cleaning without spring: > [url]http://www.hibernate.org/114.html[/url] > [code]// license-header java merge-point > // > // Attention: Generated code! Do not modify by hand! > // Generated by: HibernateUtil.vsl in andromda-hibernate-cartridge. > // > #set ($generatedFile = "${stringUtils.replace($enumeration.packageName, '.', > '/')}/HibernateUtil.java") > #if ($stringUtils.isNotBlank($enumeration.packageName)) > package $enumeration.packageName; > #end > //TODO : Code generation for JNDI : To obtain thru jndi reference Hibernate > context > //import javax.naming.*; > import org.apache.commons.logging.Log; > import org.apache.commons.logging.LogFactory; > import ${hibernateUtils.hibernatePackage}.cfg.Configuration; > import ${hibernateUtils.hibernatePackage}.Interceptor; > import ${hibernateUtils.hibernatePackage}.Session; > import ${hibernateUtils.hibernatePackage}.SessionFactory; > import ${hibernateUtils.hibernatePackage}.Transaction; > import ${hibernateUtils.hibernatePackage}.HibernateException; > /** > * Basic Hibernate helper class, handles SessionFactory, Session and > Transaction. > * <p> > * Uses a static initializer for the initial SessionFactory creation > * and holds Session and Transactions in thread local variables. All > * exceptions are wrapped in an unchecked HibernateException. > * > * @author [EMAIL PROTECTED] > */ > public class HibernateUtil { > private static Log log = LogFactory.getLog(HibernateUtil.class); > private static Configuration configuration; > private static SessionFactory sessionFactory; > private static final ThreadLocal threadSession = new ThreadLocal(); > private static final ThreadLocal threadTransaction = new ThreadLocal(); > private static final ThreadLocal threadInterceptor = new ThreadLocal(); > // Create the initial SessionFactory from the default configuration > files > static { > try { > log.debug("Create the initial SessionFactory from the default > configuration files."); > configuration = new Configuration(); > sessionFactory = > configuration.configure().buildSessionFactory(); > // We could also let Hibernate bind it to JNDI: > // configuration.configure().buildSessionFactory() > } catch (Throwable ex) { > // We have to catch Throwable, otherwise we will miss > // NoClassDefFoundError and other subclasses of Error > log.error("Building SessionFactory failed.", ex); > throw new ExceptionInInitializerError(ex); > } > } > /** > * Returns the SessionFactory used for this static class. > * > * @return SessionFactory > */ > public static SessionFactory getSessionFactory() { > /* Instead of a static variable, use JNDI: > SessionFactory sessions = null; > try { > Context ctx = new InitialContext(); > String jndiName = "java:hibernate/HibernateFactory"; > sessions = (SessionFactory)ctx.lookup(jndiName); > } catch (NamingException ex) { > throw new HibernateException(ex); > } > return sessions; > */ > return sessionFactory; > } > /** > * Returns the original Hibernate configuration. > * > * @return Configuration > */ > public static Configuration getConfiguration() { > return configuration; > } > /** > * Rebuild the SessionFactory with the static Configuration. > * > */ > public static void rebuildSessionFactory() > throws HibernateException { > synchronized(sessionFactory) { > try { > sessionFactory = > getConfiguration().buildSessionFactory(); > } catch (Exception ex) { > throw new HibernateException(ex); > } > } > } > /** > * Rebuild the SessionFactory with the given Hibernate Configuration. > * > * @param cfg > */ > public static void rebuildSessionFactory(Configuration cfg) > throws HibernateException { > synchronized(sessionFactory) { > try { > sessionFactory = cfg.buildSessionFactory(); > configuration = cfg; > } catch (Exception ex) { > throw new HibernateException(ex); > } > } > } > /** > * Retrieves the current Session local to the thread. > * <p/> > * If no Session is open, opens a new Session for the running thread. > * > * @return Session > */ > public static Session getSession() > throws HibernateException { > Session s = (Session) threadSession.get(); > try { > if (s == null) { > log.debug("Opening new Session for this > thread."); > if (getInterceptor() != null) { > log.debug("Using interceptor: " + > getInterceptor().getClass()); > s = > getSessionFactory().openSession(getInterceptor()); > } else { > s = getSessionFactory().openSession(); > } > threadSession.set(s); > } > } catch (HibernateException ex) { > throw new HibernateException(ex); > } > return s; > } > /** > * Closes the Session local to the thread. > */ > public static void closeSession() > throws HibernateException { > try { > Session s = (Session) threadSession.get(); > threadSession.set(null); > if (s != null && s.isOpen()) { > log.debug("Closing Session of this thread."); > s.close(); > } > } catch (HibernateException ex) { > throw new HibernateException(ex); > } > } > /** > * Start a new database transaction. > */ > public static void beginTransaction() > throws HibernateException { > Transaction tx = (Transaction) threadTransaction.get(); > try { > if (tx == null) { > log.debug("Starting new database transaction in > this thread."); > tx = getSession().beginTransaction(); > threadTransaction.set(tx); > } > } catch (HibernateException ex) { > throw new HibernateException(ex); > } > } > /** > * Commit the database transaction. > */ > public static void commitTransaction() > throws HibernateException { > Transaction tx = (Transaction) threadTransaction.get(); > try { > if ( tx != null && !tx.wasCommitted() > && !tx.wasRolledBack() > ) { > log.debug("Committing database transaction of > this thread."); > tx.commit(); > } > threadTransaction.set(null); > } catch (HibernateException ex) { > rollbackTransaction(); > throw new HibernateException(ex); > } > } > /** > * Commit the database transaction. > */ > public static void rollbackTransaction() > throws HibernateException { > Transaction tx = (Transaction) threadTransaction.get(); > try { > threadTransaction.set(null); > if ( tx != null && !tx.wasCommitted() && > !tx.wasRolledBack() ) { > log.debug("Tyring to rollback database > transaction of this thread."); > tx.rollback(); > } > } catch (HibernateException ex) { > throw new HibernateException(ex); > } finally { > closeSession(); > } > } > /** > * Reconnects a Hibernate Session to the current Thread. > * > * @param session The Hibernate Session to be reconnected. > */ > public static void reconnect(Session session) > throws HibernateException { > try { > session.reconnect(); > threadSession.set(session); > } catch (HibernateException ex) { > throw new HibernateException(ex); > } > } > /** > * Disconnect and return Session from current Thread. > * > * @return Session the disconnected Session > */ > public static Session disconnectSession() > throws HibernateException { > Session session = getSession(); > try { > threadSession.set(null); > if (session.isConnected() && session.isOpen()) > session.disconnect(); > } catch (HibernateException ex) { > throw new HibernateException(ex); > } > return session; > } > /** > * Register a Hibernate interceptor with the current thread. > * <p> > * Every Session opened is opened with this interceptor after > * registration. Has no effect if the current Session of the > * thread is already open, effective on next close()/getSession(). > */ > public static void registerInterceptor(Interceptor interceptor) { > threadInterceptor.set(interceptor); > } > private static Interceptor getInterceptor() { > Interceptor interceptor = > (Interceptor) threadInterceptor.get(); > return interceptor; > } > } > [/code] > and call ${enumeration.packageName}.HibernateUtil.getSessionFactory(); // > Just call the static initializer of that class > during servlet initialisation and > ${enumeration.packageName}.HibernateUtil.getSessionFactory().close(); // Free > all resources > during servlet destruction > Spring cleaning : > [url]http://ego.developpez.com/spring/[/url] chapter 3.2.2 > [url]http://www.springframework.org/docs/reference/webintegration.html[/url] > [code] > <listener> > > <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> > </listener>[/code] > > Similar problem should occur with JSF because a specific Listener exists > [url]http://www.esup-portail.org/consortium/espace/Normes_1C/portlet-spring/esup-portlet-spring-JSF.html[/url] > [code] > <listener> > > <listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class> > </listener>[/code] > To monitor your Sun JVM : > - Activate JVMPI and JMX auditing > - Jconsole from the SDK for a global view and JMX view > - jmp to list every classes and objects in the JVM > [url]http://www.khelekore.org/jmp/installing.html[/url] > Text oriented GTK tools > To monitor your JRockit JVM : > - JRockit SDK + Licence Dev > Full graphical Monitoring Tools > [url]http://dev2dev.bea.com/pub/a/2005/12/jrockit-5.html[/url] -- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.andromda.org/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira ------------------------------------------------------------------------- Take Surveys. Earn Cash. Influence the Future of IT Join SourceForge.net's Techsay panel and you'll get the chance to share your opinions on IT & business topics through brief surveys - and earn cash http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV