Permgen issue when using Spring's LocalSessionFactoryBean

758 Views Asked by At
@Configuration
public class DataSourceConfiguration
{

    @Autowired
    private Environment env;

    @Bean(destroyMethod = "close")
    public ComboPooledDataSource dataSource()
    {

        String datasourcePathStartsWith = "datasource.";

        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        try
        {
            dataSource.setContextClassLoaderSource("library");
            dataSource.setPrivilegeSpawnedThreads(true);
            dataSource.setDriverClass("oracle.jdbc.driver.OracleDriver");
            dataSource.setJdbcUrl(env.getProperty(datasourcePathStartsWith + "url"));
            dataSource.setUser(env.getProperty(datasourcePathStartsWith + "user"));
            dataSource.setPassword(env.getProperty(datasourcePathStartsWith + "password"));
            dataSource.setMinPoolSize(Integer.parseInt(env.getProperty(datasourcePathStartsWith + "minPoolSize")));
            dataSource.setMaxPoolSize(Integer.parseInt(env.getProperty(datasourcePathStartsWith + "maxPoolSize")));
            dataSource.setCheckoutTimeout(Integer.parseInt(env.getProperty(datasourcePathStartsWith + "checkoutTimeout")));
            dataSource.setMaxIdleTime(Integer.parseInt(env.getProperty(datasourcePathStartsWith + "maxIdleTime")));
            dataSource.setMaxStatements(Integer.parseInt(env.getProperty(datasourcePathStartsWith + "maxStatements")));
        }
        catch (PropertyVetoException e)
        {
            e.printStackTrace();
        }
        return dataSource;
    }


}

Here is my code. It works "fine" on Tomcat 7 - when I redeploy application many times and use "Find leaks" feature, it shows nothing. But if I add sessionFactory, problems appears every redeploy:

The following web applications were stopped (reloaded, undeployed), but their classes from previous runs are still loaded in memory, thus causing a memory leak (use a profiler to confirm):   /testPermGen
  /testPermGen
  /testPermGen

    @Bean
    public LocalSessionFactoryBean sessionFactory(DataSource dataSource)
    {
        LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        return sessionFactoryBean;
    }

I tried this code, without wiring to dataSource, no success. Looks like the problem is with unclosed LocalSessionFactoryBean on application stop.

    @Bean(destroyMethod = "destroy")
    public LocalSessionFactoryBean sessionFactory()
    {
        LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
        return sessionFactoryBean;
    }

Tomcat is running with following flags:

-XX:MaxPermSize=128m
-XX:+UseConcMarkSweepGC
-XX:+CMSClassUnloadingEnabled
-XX:+CMSPermGenSweepingEnabled

It is possible to fix this problem with memory leaks?

UPDATE:

I've uploaded the whole project (4 files) on GitHub to reproduce the memory leak: https://github.com/anton-09/TestPermGen

Mattias Jiderhamn's classloader-leak-prevention didn't help a lot, I've got this log in Tomcat 7:

июл 03, 2017 11:44:27 AM se.jiderhamn.classloader.leak.prevention.JULLogger warn
WARNING: Waiting for Thread 'Thread[Resource Destroyer in BasicResourcePool.close(),5,main]' of type com.mchange.v2.resourcepool.BasicResourcePool$5 loaded by protected ClassLoader with contextClassLoader = protected ClassLoader or child for 5000 ms. Thread stack trace: 
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.read(SocketInputStream.java:152)
    at java.net.SocketInputStream.read(SocketInputStream.java:122)
    at oracle.net.ns.Packet.receive(Packet.java:300)
    at oracle.net.ns.DataPacket.receive(DataPacket.java:106)
    at oracle.net.ns.NetInputStream.getNextPacket(NetInputStream.java:315)
    at oracle.net.ns.NetInputStream.read(NetInputStream.java:260)
    at oracle.net.ns.NetInputStream.read(NetInputStream.java:185)
    at oracle.net.ns.NetInputStream.read(NetInputStream.java:102)
    at oracle.jdbc.driver.T4CSocketInputStreamWrapper.readNextPacket(T4CSocketInputStreamWrapper.java:124)
    at oracle.jdbc.driver.T4CSocketInputStreamWrapper.read(T4CSocketInputStreamWrapper.java:80)
    at oracle.jdbc.driver.T4CMAREngine.unmarshalUB1(T4CMAREngine.java:1137)
    at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:290)
    at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:192)
    at oracle.jdbc.driver.T4C7Ocommoncall.doOLOGOFF(T4C7Ocommoncall.java:61)
    at oracle.jdbc.driver.T4CConnection.logoff(T4CConnection.java:543)
    at oracle.jdbc.driver.PhysicalConnection.close(PhysicalConnection.java:3984)
    at com.mchange.v2.c3p0.impl.NewPooledConnection.close(NewPooledConnection.java:642)
    at com.mchange.v2.c3p0.impl.NewPooledConnection.closeMaybeCheckedOut(NewPooledConnection.java:255)
    at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool$1PooledConnectionResourcePoolManager.destroyResource(C3P0PooledConnectionPool.java:622)
    at com.mchange.v2.resourcepool.BasicResourcePool$1DestroyResourceTask.run(BasicResourcePool.java:1076)
    at com.mchange.v2.resourcepool.BasicResourcePool.destroyResource(BasicResourcePool.java:1101)
    at com.mchange.v2.resourcepool.BasicResourcePool.destroyResource(BasicResourcePool.java:1062)
    at com.mchange.v2.resourcepool.BasicResourcePool.access$100(BasicResourcePool.java:44)
    at com.mchange.v2.resourcepool.BasicResourcePool$5.run(BasicResourcePool.java:1316)
июл 03, 2017 11:44:27 AM se.jiderhamn.classloader.leak.prevention.JULLogger info
INFO: Thread 'Thread[Resource Destroyer in BasicResourcePool.close(),5,main]' of type com.mchange.v2.resourcepool.BasicResourcePool$5 loaded by protected ClassLoader with contextClassLoader = protected ClassLoader or child no longer alive - no action needed.

Looks like everithing ok, but Tomcat's "find leaks" reports about new memory leak.

SOLUTION

The problem was with JBoss logging included in Hibernate dependency. I excluded jboss-logging dependency from hibernate-core, copied jboss-logging-3.3.0.Final.jar to Tomcat's "lib" folder and the problem went away.

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>${hibernate.version}</version>
    <exclusions>
        <exclusion>
            <groupId>org.jboss.logging</groupId>
            <artifactId>jboss-logging</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.jboss.logging</groupId>
            <artifactId>jboss-logging-annotations</artifactId>
        </exclusion>
    </exclusions>
</dependency>
1

There are 1 best solutions below

10
Mattias Jiderhamn On BEST ANSWER

There are many third party libraries that may cause ClassLoader leaks. If you want to track down the offender in your case (which could be both fun and educational!), I recommend following the instructions in this blog post of mine. To get rid of the issue, simply add my ClassLoader Leak Prevention library to your application. The offender is sometimes also revealed just by watching the logs after adding that library.