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]

Reply via email to