@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>
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.