Search code examples
springhibernatetomcatjpajboss-logging

How to avoid Classloader Leak with JPA, Hibernate and Spring on Tomcat


The Open J2EE Web Template is a showcase application for wicket - JPA with Spring and Hibernate that runs on a Tomcat7 servlet container. Its Maven build script appears to use the components in a standard way.

However, it is affected by an application classloader memory leak when it is undeployed from Tomcat.

Tomcat's "Find Leaks" button confirms the leak. When deployed on Tomcat with VM option -XX:+HeapDumpOnOutOfMemoryError, the generated heap dump can be analysed with Eclipse Memory Analyzer Tool (MAT). MAT identifies the class java.util.logging.Level$KnownLevel as the culprit that prevents garbage collection.

Debugging of the KnownLevel constructor reveals the following stack trace:

java.util.logging.Level$KnownLevel.add(Level.java:477) java.util.logging.Level.(Level.java:212) java.util.logging.Level.(Level.java:190) org.jboss.logging.JDKLevel.(JDKLevel.java:35) org.jboss.logging.JDKLevel.(JDKLevel.java:42) org.jboss.logging.JDKLogger.translate(JDKLogger.java:78) org.jboss.logging.JDKLogger.isEnabled(JDKLogger.java:85) org.jboss.logging.Logger.debugf(Logger.java:563) org.jboss.logging.LoggerProviders.find(LoggerProviders.java:37) org.jboss.logging.LoggerProviders.(LoggerProviders.java:32) org.jboss.logging.Logger.getLogger(Logger.java:2163) org.jboss.logging.Logger.getMessageLogger(Logger.java:2259) org.jboss.logging.Logger.getMessageLogger(Logger.java:2214) org.hibernate.ejb.Ejb3Configuration.(Ejb3Configuration.java:144) org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFactory(HibernatePersistence.java:74) org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:268) org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:310) org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1514) org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1452) org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519) org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456) org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:294) org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:225) org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291) org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193) org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:567) org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:913) org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:464) org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:385) org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:284) org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:111) org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4939)

If I understand Classloader leaks: the dreaded "java.lang.OutOfMemoryError: PermGen space" exception correctly, then this classloader leak is to be expected.

What would be the recommended method to avoid this, or what would be an alternative Spring/JPA template web application on Tomcat?


Solution

  • I have seen this same webapp memory leak on Tomcat7 + OpenJPA2.4.1 + ValidationAPI1.x + hibernate-validator-5.2.1 + jboss-logging.jar

    Problem is indeed jboss-logging.jar creates a subclass of java.util.logging.Level instances. May not be a problem if jar is provided by J2EE container but will happen with mywebapp/WEB-INF/lib distribution. I have created a fork of package to disable subclassing. Problems went away and webapp hot redeployments work fine.

    Heapdump of GC root path indicates a source of problem, JDKLevel subclass keeps a webapp in memory and soon JVM run out of PermGen memory.

    this     - value: org.apache.catalina.loader.WebappClassLoader #2
     <- <classLoader>     - class: org.jboss.logging.JDKLevel, value: org.apache.catalina.loader.WebappClassLoader #2
      <- <class>     - class: org.jboss.logging.JDKLevel, value: org.jboss.logging.JDKLevel class JDKLevel
       <- levelObject     - class: java.util.logging.Level$KnownLevel, value: org.jboss.logging.JDKLevel #6
        <- [1]     - class: java.lang.Object[], value: java.util.logging.Level$KnownLevel #12
         <- elementData     - class: java.util.ArrayList, value: java.lang.Object[] #5160 (10 items)
          <- value     - class: java.util.HashMap$Entry, value: java.util.ArrayList #3532
           <- [0]     - class: java.util.HashMap$Entry[], value: java.util.HashMap$Entry #21639
            <- table     - class: java.util.HashMap, value: java.util.HashMap$Entry[] #280 (16 items)
             <- intToLevels (sticky class)     - class: java.util.logging.Level$KnownLevel, value: java.util.HashMap #375
    

    edit Created JBoss Jira ticket for this bug (https://issues.jboss.org/browse/JBLOGGING-118)