[ 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

Reply via email to