Проблема Permgen при использовании Spring LocalSessionFactoryBean

@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;
    }


}

Вот мой код. Он работает «отлично» на Tomcat 7 - когда я много раз переустанавливаю приложение и использую функцию «Найти утечки», он ничего не показывает. Но если я добавлю sessionFactory, проблемы появятся при каждом повторном развертывании:

Следующие веб-приложения были остановлены (перезагружены, не развернуты), но их классы из предыдущих запусков по-прежнему загружаются в память, что вызывает утечку памяти (для подтверждения используйте профилировщик): /testPermGen
/testPermGen
/testPermGen

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

Я попробовал этот код без подключения к источнику данных, но безуспешно. Похоже, проблема в незакрытом LocalSessionFactoryBean при остановке приложения.

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

Tomcat работает со следующими флагами:

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

Можно ли исправить эту проблему с утечками памяти?

ОБНОВИТЬ:

Я загрузил весь проект (4 файла) на GitHub, чтобы воспроизвести утечку памяти: https://github.com/anton-09/TestPermGen

Предотвращение утечки загрузчика классов Маттиаса Джидерхамна не очень помогло, у меня есть этот журнал в 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.

Вроде бы все в порядке, но "найти утечки" Tomcat сообщает о новой утечке памяти.

РЕШЕНИЕ

Проблема заключалась в том, что ведение журнала JBoss было включено в зависимость Hibernate. Я исключил зависимость jboss-logging от hibernate-core, скопировал jboss-logging-3.3.0.Final.jar в папку «lib» Tomcat, и проблема исчезла.

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

person AHTOH    schedule 30.06.2017    source источник
comment
Попробуйте другую реализацию пула соединений? Например, Spring Boot поддерживает (в порядке предпочтения): Tomcat, HikariCP, DBCP (устаревший), DBCP2.   -  person Strelok    schedule 03.07.2017


Ответы (1)


Существует многие сторонние библиотеки, которые могут вызвать утечку ClassLoader. Если вы хотите найти преступника в вашем случае (что может быть и весело, и познавательно!), я рекомендую следовать инструкциям в этот мой пост в блоге. Чтобы избавиться от этой проблемы, просто добавьте в свое приложение мою библиотеку предотвращения утечек ClassLoader. Нарушителя иногда также выявляют, просто просматривая логи после добавления этой библиотеки.

person Mattias Jiderhamn    schedule 01.07.2017
comment
Я знаю нарушителя - это библиотека C3P0. Ваш загрузчик классов не помог, я обновил свой вопрос с помощью журналов Tomcat. - person AHTOH; 03.07.2017
comment
Когда вы говорите, что это не помогло, что это значит? Сервер в конечном итоге падает с OutOfMemoryError? Tomcat все еще ведет журнал ... но его классы из предыдущих запусков все еще загружаются в память ...? Проблема может заключаться не в самом C3PO, а в драйвере Oracle, который, как известно, вызывает утечки. Можно ли попробовать другую базу данных? - person Mattias Jiderhamn; 03.07.2017
comment
Tomcat сообщает о классах, все еще загруженных в память. После ряда операций остановки/запуска я получил ошибку PermGen OutOfMemory. Я ошибся насчет C3P0 (возможно). Я только что попробовал другую базу данных (mysql) и другую библиотеку пула соединений (hikari) с тем же результатом! Единственный способ решить эту проблему — полностью прокомментировать компонент sessionFactory!! Может быть, это вина Spring? - person AHTOH; 04.07.2017
comment
Мне было бы интересно найти причину этого. Пожалуйста, попробуйте получить дамп кучи. Два способа сделать это описаны в java.jiderhamn.se/2011/12/11/, но есть и другие варианты, в зависимости от вашей ОС (kill -3, Ctrl+Break). Если вы не можете проанализировать его самостоятельно, может быть, вы могли бы предоставить его мне для анализа? - person Mattias Jiderhamn; 04.07.2017
comment
Я помещаю дамп кучи здесь (15 МБ): dropbox.com/s/jx40c01nvzuehxg /java_pid6536.rar?dl=0 Я также пытался проанализировать это с помощью Eclipse MAT и вижу проблемы с классом com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread - person AHTOH; 04.07.2017
comment
Утечка вызвана тем, что JBoss Logging ведет журнал через JUL (java.util.logging) с использованием пользовательского уровня журнала org.jboss.logging.JDKLevel (который находится в вашем WAR). Вы сможете обойти это, переместив JBoss Logging на уровень сервера приложений или перенастроив ведение журнала. - person Mattias Jiderhamn; 04.07.2017
comment
Великолепно! Я исключил зависимость jboss-logging от hibernate-core, скопировал jboss-logging-3.3.0.Final.jar в папку lib Tomcat, и ошибка исчезла. Можете ли вы сказать мне (можно через скриншот), как вы нашли эту утечку? Я хочу попробовать исправить мое полное приложение самостоятельно - person AHTOH; 05.07.2017
comment
Как найти утечки в дампе, описано в сообщении блога, на которое есть ссылка в ответе, который мы комментируем; java.jiderhamn.se/2011/12/11/ - person Mattias Jiderhamn; 05.07.2017
comment
Я добавил автоматическое предотвращение утечек для пользовательских java.util.logging.Level. Не могли бы вы скомпилировать github.com/mjiderhamn/classloader-leak-prevention из исходников и попробовать его с вашим проектом (с jboss-logging-3.3.0.Final.jar, удаленным из Tomcat и повторно добавленным в WAR), прежде чем я выпущу его в Maven Central? - person Mattias Jiderhamn; 09.07.2017
comment
Предотвращение этой утечки было выпущено в версии 2.3.0 моей библиотеки. - person Mattias Jiderhamn; 18.07.2017