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
 Assigned to: 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