I believe I am seeing a memory leak that occurs when deploying or more precisely undeploying a web application through the Tomcat manager. I've done some analysis using a stripped down web application, JProbe, and code inspection. I would not presume to know the Tomcat source nor have done a complete and thorough analysis, but I would like to share my observations and more importantly, solicit feedback from the Tomcat user/development community.
Environment: RedHat 8.0, JDK 1.4.1, Tomcat 4.1.21 Beta Synopsis of problem: We are deploying and undeploying our web applications through the Tomcat Manager. In the case of one of our web applications, redeploying 3-4 times resulted in an OutOfMemoryError in Tomcat's JVM. Initially, we thought this was due to several daemon Threads that were not Servlet lifecycle aware. But even after fixing these, we were still running out of memory. Suspecting that our classes were not being garbage collected (note the distinction between object garbage collection and class garbage collection) and might be pinned by classes that exist higher in the ClassLoader hierarchy (in common, shared, or possibly even server), I decided to try profiling using JProbe (http://java.quest.com/jprobe/jprobe.shtml) and a VERY simple web application. This web application is composed of a single Servlet that does nothing but allocate a 1,000,000 element byte array in its init() method and nulls it in its destroy() method. I deployed and undeployed several times running under JProbe's memory debugger and did observe a small memory leak of org.apache.* classes/instances. Analysis: These are the org.apache instances that do not appear to be garbage collected after a deploy/undeploy cycle: Class Count --------------------------------------------------------------------- org.apache.catalina.LifecycleListener[] 4 org.apache.catalina.Valve[] 1 org.apache.catalina.core.ApplicationContext 1 org.apache.catalina.core.ApplicationContextFacade 1 org.apache.catalina.core.NamingContextListener 1 org.apache.catalina.core.StandardContext 1 org.apache.catalina.core.StandardContextMapper 1 org.apache.catalina.core.StandardContextValve 1 org.apache.catalina.core.StandardPipeline 1 org.apache.catalina.deploy.ApplicationParameter[] 1 org.apache.catalina.deploy.NamingResources 1 org.apache.catalina.deploy.SecurityConstraint[] 1 org.apache.catalina.deploy.FilterMap[] 1 org.apache.catalina.loader.WebappClassLoader 1 org.apache.catalina.loader.WebappLoader 1 org.apache.catalina.session.StandardManager 1 org.apache.catalina.startup.ContextConfig 1 org.apache.catalina.util.LifecycleSupport 4 org.apache.commons.collections.LRUMap 1 org.apache.commons.collections.SequencedHashMap$Entry 6 org.apache.naming.NameParserImpl 2 org.apache.naming.NamingContext 3 org.apache.naming.NamingEntry 4 org.apache.naming.TransactionRef 1 org.apache.naming.resources.ImmutableNameNotFoundException 1 org.apache.naming.resources.ProxyDirContext 1 org.apache.naming.resources.ProxyDirContext$CacheEntry 5 org.apache.naming.resources.ResourceAttributes 3 org.apache.naming.resources.WARDirContext 2 org.apache.naming.resources.WARDirContext$WARResource 2 org.apache.naming.resources.WARDirContext$Entry 2 org.apache.naming.resources.WARDirContext$Entry[] 2 Initially, I focused on the org.apache.catalina.core.StandardContext class. It seemed like a nice entry point that scopes the Catalina classes supporting a web application deployment. It appears that an instance is pinned in several locations: 1. org.apache.naming.ContextBindings.bindContext() is called (in org.apache.catalina.core.NamingContextListener.lifecycleEvent() given a org.apache.catalina.LifeCycleEvent whose getType() is org.apache.catalina.Lifecycle.START_EVENT). This puts StandardContext into a static Hashtable within ContextBindings. This Hashtable entry is removed by a call to ContextBindings.unbindContext(). unbindContext() appears to never be called. 2. An org.apache.jasper.logging.DefaultLogger instance (which implements org.apache.jasper.logging.Logger) is created in org.apache.jasper.servlet.JspServlet.init(). DefaultLogger's setName() method is called resulting in the Logger being placed into a static Hashtable. Entries in this Hashtable are removed via the Logger.close() and Logger.removeLogger(...) methods, neither of which appear to be called. DefaultLogger refers to a StandardContext via the following chain of references: DefaultLogger -> org.apache.catalina.core.ApplicationContextFacade -> org.apache.catalina.core.ApplicationContext -> StandardContext 3. The DefaultLogger created in JspServlet is also referenced by a static field in org.apache.jasper.Constants. This field does not appear to be cleared. 4. org.apache.catalina.core.StandardHostDeployer has a static org.apache.catalina.Context field that is set to the suspect StandardContext after a call to its addChild() method (called reflectively by org.apache.commons.digester.Digester). This field is not unset unless the install(URL, URL) method is called (but not if the other install(String, URL) or either of the remove() methods is called). This context can be replaced if addChild() is called again, but would this happen only if another deployment occured? If so, StandardContext is still "pinned" if the web application is left undeployed and a subsequent deployment does not occur. 5. One of the Digester instances can also periodically hold onto a StandardContext (as its root), but this reference can and appears to be replaced (via Digester.push() when its stack is empty). I have not analyzed when this may be the case, so similar to (4), it seems possible that StandardContext will be pinned if the Digester instance is not "reset." At this point in time, I have not analyzed memory leaks beyond references to StandardContext. Many other "leaky" instances can be traced back to StandardContext (ApplicationContext, ApplicationContextFacade, StandardManager, etc...). The number of instances that appear to be leaking and the size of these instances is fairly small. I would guess less than 2K. However, I suspect that these instances are pinnning classes. By instrumenting our code, I have been able to determine that instances are indeed being garbage collected. On the other hand, I have been able to instrument WebappClassLoader and have not seen it finalize. In our production environment, we are deploying a Jetspeed portal which contains hundreds of classes which I believe can explain the limited number of times we are able to re-deploy before running out of memory. If you've managed to read through to here, any ideas or pointers would be greatly appreciated. Thanks, Ted Chen ([EMAIL PROTECTED]) --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]