Подтвердить что ты не робот

Утечка памяти при остановке или повторном развертывании - Spring 3.1.2, Hibernate 4.1.0, Spring Data-Jpa 1.1.0, Tomcat 7.0.30

РЕДАКТИРОВАТЬ 1

2013/06/07 - В то время как у меня все еще возникает эта проблема, это еще только влияет на повторное развертывание. Поскольку исходный вопрос был опубликован, мы обновили несколько вещей. Вот наша новая версия (которая все еще показывает проблему):

<properties>
    <!-- Persistence and Validation-->
    <hibernate.version>4.1.0.Final</hibernate.version>
    <hibernate.jpa.version>1.0.1.Final</hibernate.jpa.version>
    <javax.validation.version>1.0.0.GA</javax.validation.version>
    <querydsl.version>2.2.5</querydsl.version>
    <spring.jpa.version>1.2.0.RELEASE</spring.jpa.version>
    <spring.ldap.version>1.3.1.RELEASE</spring.ldap.version>

    <!-- Spring and Logging -->
    <spring.version>3.1.3.RELEASE</spring.version>
    <spring.security.version>3.1.3.RELEASE</spring.security.version>
    <slf4j.version>1.6.4</slf4j.version>
    <jackson.version>1.9.9</jackson.version>

    <cglib.version>3.0</cglib.version>
</properties>

Как вы можете видеть, это в основном просто фреймворк Spring и Spring (Data) Jpa bump. Мы также перешли на Tomcat 7.0.39. CGLIB (который не упоминался ранее, но был включен) также был загружен до 3.0

Вот некоторые из вещей, которые я пытался устранить, но не повезло:

  • Изменен наш POM (с использованием Maven), чтобы настроить наш драйвер базы данных как имеющийся
  • Добавлена ​​зависимость для SLF4J к включению jul-to-slf4j, поскольку было упомянуто, что здесь может быть потенциальная утечка.
  • Протестируйте защитник утечки Classload (https://github.com/mjiderhamn/classloader-leak-prevention). Это, похоже, сработало (поскольку оно спамало мои журналы во время undeploy/shutdown, указав тонну вещей, которые он чистил), но после использования VisualVM мое пространство perm gen не было исправлено. Это может быть полезно для других...
  • Попробовал перечислить все исключения в моей POM с помощью инструмента анализа зависимостей Maven (в IntelliJ в окне Maven Projects вы можете щелкнуть правой кнопкой мыши зависимости и выбрать "Показать зависимости" и "Сменить-Удалить все красные зависимости" ). Это не помогло, но уменьшило размер моего WAR файла примерно на миллион или около того.
  • Реализована конфигурация сохраняемости JPA следующим образом (обратите внимание на комментарии) на основе неразрешенного отчета об ошибке от Spring (https://jira.springsource.org/browse/SPR-9274):

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { // Important line (notice entityManagerFactory is 'provided/autowired'
        return new JpaTransactionManager(entityManagerFactory);
    }
    
    @Bean
    public EntityManagerFactory getEntityManagerFactory(DataSource dataSource) { // Important line (notice dataSource is 'provided/autowired'
    
        LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setPackagesToScan("my.scanned.domain");
    
        AbstractJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);
        vendorAdapter.setShowSql(false);
        vendorAdapter.setDatabase(Database.POSTGRESQL);
        vendorAdapter.setDatabasePlatform("org.hibernate.dialect.PostgreSQL82Dialect");
    
        factoryBean.setJpaVendorAdapter(vendorAdapter);
    
        Map<String, Object> properties = new HashMap<>();
        properties.put("hibernate.ejb.naming_strategy", "org.hibernate.cfg.ImprovedNamingStrategy");
        properties.put( "hibernate.bytecode.provider", "cglib" );   // Suppose to help java pergem space issues with hibernate
    
        factoryBean.setPersistenceProvider(new HibernatePersistence());
        factoryBean.setJpaPropertyMap(properties);
        factoryBean.setPersistenceUnitName("myPersistenace");
        factoryBean.afterPropertiesSet();
    
        return factoryBean.getObject(); // Important line
    }
    
    @Bean
    public PersistenceExceptionTranslator getHibernateExceptionTranslator() { // Required
        return new HibernateExceptionTranslator();
    }
    
    @Bean
    public DataSource getDataSource() {
        JndiDataSourceLookup lookup = new JndiDataSourceLookup();
        DataSource dataSource = lookup.getDataSource("java:comp/env/jdbc/myLookup");
    
        lookup = null;
    
        return dataSource;
    }
    
  • Создал "ThreadImmolator" согласно qaru.site/info/123728/... в следующем SO-вопросе: Возможно ли это создать утечка памяти в Tomcat?. Похоже, что это почти то же самое, что и обнаружение TreadLocal Leak Detection выше - у меня не было жалобщика Tomcat Leak; однако пространство perm gen по-прежнему не восстанавливалось после повторного развертывания.
    • Моя первая попытка состояла в том, чтобы добавить @PreDestory в мой WebConfig (тот же bean, который имеет @EnableWebMvc), чтобы попытаться вызвать его при закрытии. Химическая завивка Генерал остался.
    • Моя вторая попытка заключалась в подклассе ContextLoaderListener, переопределении функций ContextDestoryed() и inline. Химическая завивка Генерал остался.
  • Так как ThreadImmolator не помог (или оказался не помог), я попробовал решение, предложенное в qaru.site/info/123504/... в следующем SO-вопросе: 'Как очистить threadlocals. Это заставило меня попробовать: https://weblogs.java.net/blog/jjviana/archive/2010/06/09/dealing-glassfish-301-memory-leak-or-threadlocal-thread-pool-bad-idehttp://blog.igorminar.com/2009/03/identifying-threadlocal-memory-leaks-in.html.

В этот момент у меня закончились идеи.

Я также попытался изучить анализ кучи, используя это как отправную точку (http://cdivilly.wordpress.com/2012/04/23/permgen-memory-leak/). Я могу найти загрузчик классов, который не был очищен, и я вижу, что он все еще ссылается на все классы, связанные с Spring. Я также пробовал поиск org.springframework.core.NamedThreadLocal, и я все еще вижу, как они появляются в куче после сдачи дампа после запуска ThreadImmolator, Thread Leak Preventor и других "жестких решений", которые я пробовал выше.

Возможно, приведенная выше информация может помочь кому-то, но я продолжу пересматривать эту SO с новой информацией или решить эту проблему.

Проблема

У приложения нет проблем, запущенных в течение нескольких дней подряд на рабочем сервере, но когда я выполняю развертывание для обновлений, программа Tomcat Manager будет жаловаться на утечки (если я нажимаю на поиск утечек). Если я выполняю 6-10 разводок, в конечном итоге у Tomcat заканчивается память с ошибкой памяти PermGen, и мне нужно перезапустить службу Tomcat, и все возвращается в нормальное состояние.

Когда я запускаю/отлаживаю приложение локально и выполняю некоторые действия, требующие доступа через Jpa/Hibernate (т.е. я вхожу в систему или запрашиваю список из JpaRepository), а затем выключение приложения, я получаю следующие сообщения в своем отладочном выходе от Tomcat:

Октябрь 03, 2012 2:55:13 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE: веб-приложение [/] создал ThreadLocal с ключом типа [org.springframework.core.NamedThreadLocal] (значение [Transactional ресурсы]) и значение типа [java.util.HashMap] (значение [{public abstract java.lang.Object org.springframework.data.repository.CrudRepository.findOne(java.io.Serializable) [email protected]}]) но не удалось удалить его, когда веб-приложение было остановлено. Потоки со временем будут обновляться, чтобы попытаться избежать вероятной памяти протечь.

Октябрь 03, 2012 2:55:13 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE: веб-приложение [/] создал ThreadLocal с ключом типа [org.springframework.core.NamedThreadLocal] (значение [Transactional ресурсы]) и значение типа [java.util.HashMap] (значение [{public abstract java.util.List org.springframework.data.jpa.repository.JpaRepository.findAll() = [email protected]}]) но не удалось удалить его, когда веб-приложение было остановлено. Потоки со временем будут обновляться, чтобы попытаться избежать вероятной памяти протечь.

Октябрь 03, 2012 2:55:13 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE: веб-приложение [/] создал ThreadLocal с ключом типа [org.springframework.core.NamedThreadLocal] (значение [Transactional ресурсы]) и значение типа [java.util.HashMap] (значение [{public абстрактный java.lang.Iterable org.springframework.data.querydsl.QueryDslPredicateExecutor.findAll(com.mysema.query.types.Predicate) [email protected]}]) но не удалось удалить его, когда веб-приложение было остановлено. Потоки со временем будут обновляться, чтобы попытаться избежать вероятной памяти протечь.

Октябрь 03, 2012 2:55:13 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE: веб-приложение [/] создал ThreadLocal с ключом типа [org.springframework.core.NamedThreadLocal] (значение [Transactional ресурсы]) и значение типа [java.util.HashMap] (значение [{public abstract data.domain.UserAccount UserAccountRepository.findByUserName(java.lang.String) [email protected]}]) но не удалось удалить его, когда веб-приложение было остановлено. Потоки со временем будут обновляться, чтобы попытаться избежать вероятной памяти протечь.

и т.д. и т.д.

Конфигурация

Spring настраивается с помощью аннотаций, и я также использую Postgres 8.4 в качестве базы данных.

JPA настраивается с помощью аннотаций (jpa-repository-context.xml просто говорит, чтобы искать этот класс):

@Configuration
@EnableTransactionManagement
@ImportResource( "classpath*:*jpa-repository-context.xml" )
@ComponentScan( basePackages = { "data.repository" } )
public class PersistenceJpaConfig
{
    @Bean
        public LocalContainerEntityManagerFactoryBean entityManagerFactory()
        {
            LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
            factoryBean.setDataSource( dataSource() );
            factoryBean.setPackagesToScan( new String[] { "data.domain" } );

            // Setup vendor specific information. This will depend on the chosen DatabaseType
            HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
            vendorAdapter.setGenerateDdl( true );
            vendorAdapter.setShowSql( false );
            vendorAdapter.setDatabasePlatform( "org.hibernate.dialect.PostgreSQL82Dialect" );

            factoryBean.setJpaVendorAdapter( vendorAdapter );

            Map<String, Object> properties = new HashMap<String, Object>();
            properties.put( "hibernate.ejb.naming_strategy", "org.hibernate.cfg.ImprovedNamingStrategy" );

            factoryBean.setJpaPropertyMap( properties );

            return  factoryBean;
        }

        @Bean
        public DataSource dataSource()
        {
            JndiDataSourceLookup lookup = new JndiDataSourceLookup();
            DataSource dataSource;

            dataSource = lookup.getDataSource( "java:comp/env/jdbc/postgres" );


            return dataSource;
        }

        @Bean
        public PlatformTransactionManager transactionManager()
        {
            JpaTransactionManager transactionManager = new JpaTransactionManager();
            transactionManager.setEntityManagerFactory( entityManagerFactory().getObject() );

            return transactionManager;
        }
}

Пример репозитория:

public interface UserAccountRepository extends JpaRepository<UserAccount, Long>, QueryDslPredicateExecutor<UserAccount> {
}

Все мои репозитории доступны через класс службы, который зарегистрирован как @Component в Spring. Это делается для удаления доступа к репозиторию с контроллеров Spring:

@Component
public class UserAccountService {

    @Autowired
    private UserAccountRepository userAccountRepository;

    public List<UserAccount> getUserAccounts() {
        return userAccountRepository.findAll();
    }
    ...
}

И вот версии для различных компонентов, используемых в Maven pom.xml:

<properties>
        <!-- Persistence and Validation-->
        <hibernate.version>4.1.0.Final</hibernate.version>
        <hibernate.jpa.version>1.0.1.Final</hibernate.jpa.version>
        <javax.validation.version>1.0.0.GA</javax.validation.version>
        <querydsl.version>2.2.5</querydsl.version>
        <spring.jpa.version>1.1.0.RELEASE</spring.jpa.version>

        <!-- Spring and Logging -->
        <spring.version>3.1.2.RELEASE</spring.version>
        <spring.security.version>3.1.2.RELEASE</spring.security.version>
        <slf4j.version>1.6.4</slf4j.version>
        <jackson.version>1.9.9</jackson.version>

        <!-- Testing Suites -->
        <selenium.version>2.24.1</selenium.version>
</properties>

Вопросы

  • Что вызывает утечку памяти и как ее исправить?
  • Как я могу отладить эту проблему?
  • Есть ли что-нибудь в моем конфигурационном наборе, которое можно улучшить?

У меня действительно не хватает идей по решению этой проблемы.

4b9b3361

Ответ 1

Я думаю, что у вас могут быть два вида утечек одновременно. Spring предупреждает вас о нормальной утечке памяти "кучи". Это еще не вызвало вас, потому что... ваше перераспределение также вызывает чрезмерное использование PermGen, и эта проблема поражает вас в первую очередь. Для получения информации о втором типе утечки см. Работа с" java.lang.OutOfMemoryError: пространство PermGen " ошибка [Спасибо duffymo]

[обновление]

Поскольку вы говорите, что предложения в приведенной выше ссылке не помогли, единственные другие предложения, о которых я могу думать, следующие:

  • убедитесь, что ваш Spring beans очищается, когда Spring выключается
  • каждый bean, который выделяет ресурс (что вряд ли будет собирать мусор) в конструкторе или метод init должен иметь метод destroy, чтобы освободить его
  • это особенно верно для любого beans, который ссылается на любые классы в модуле spring -data, catalina жалуется на класс в этом модуле
  • увеличьте свое пространство перменто. Даже если это предложение не устранило проблему, добавив что-то вроде следующего, чтобы эти сбои происходили реже.

Try -XX: MaxPermSize = 256 м, и если он сохраняется, попробуйте -XX: MaxPermSize = 512m

Идеальный подход uber-грубая сила заключается в постепенном изъятии вещей из вашего приложения, пока проблема не исчезнет. Это поможет вам сузить его до такой степени, чтобы определить, является ли это проблемой для вашего кода или ошибка в Spring, Hibernate и т.д.

Ответ 2

slash3tc - в первую очередь спасибо за отличную запись, это помогло мне подключить несколько утечек в моем приложении. У меня была та же самая проблема, которая оказалась вызовом DAO spring -data-jpa без активной транзакции. Это может произойти с планировщиком или аннотацией @PostConstruct (ранее InitializingBean). Убедитесь, что вы правильно настроили управление транзакциями и что любые вызовы JPA происходят при активной транзакции (т.е. Путем аннотирования методов обслуживания с помощью @Transactional).

Изменить: Кажется, это (также?) ошибка со старыми версиями spring -data-jpa, которая была исправлена ​​в 1.4.3

Подробнее: http://georgovassilis.blogspot.nl/2014/01/tomcat-spring-and-memory-leaks-when.html

Ответ 3

У меня была аналогичная проблема, на самом деле 3 проблемы:

  • Драйвер JDBC не выпущен
  • Соединение не передается
  • Кучи не выпускаются

У каждого из них есть другое решение:

Ответ 4

У меня была аналогичная проблема, и я только что исправил ее.

[org.springframework.core.NamedThreadLocal] (значение [Транзакционные ресурсы]

Он может быть создан TransactionSynchronizationManager.

private static final ThreadLocal<Map<Object, Object>> resources =
        new NamedThreadLocal<Map<Object, Object>>("Transactional resources");

private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
        new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");

private static final ThreadLocal<String> currentTransactionName =
        new NamedThreadLocal<String>("Current transaction name");

private static final ThreadLocal<Boolean> currentTransactionReadOnly =
        new NamedThreadLocal<Boolean>("Current transaction read-only status");

private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
        new NamedThreadLocal<Integer>("Current transaction isolation level");

private static final ThreadLocal<Boolean> actualTransactionActive =
        new NamedThreadLocal<Boolean>("Actual transaction active");

В моем случае были пропущены NamedThreadLocals, кроме "Транзакционных ресурсов". Поэтому я добавил следующий код в последнюю строку contextDestroyed.

TransactionSynchronizationManager.clear();

Я думаю, вы также можете освободить ресурсы с добавлением:

Map<Object, Object> ojb = TransactionSynchronizationManager.getResourceMap();
for (Object key : ojb.keySet()) {
    TransactionSynchronizationManager.unbindResource(key);
}

Дополнительно к этому мне нужно было сделать следующее:

  • Удален драйвер PostgreSQL из tomcat/libs. Я уже добавил зависимость в build.gradle, чтобы они дублировались.
  • Обновлена ​​библиотека hibernate-validator до последней версии

    compile ('org.springframework.boot:spring-boot-starter-thymeleaf:' + bootVersion + '.RELEASE') {
        exclude module: 'spring-boot-starter-tomcat'
        exclude module: 'hibernate-validator'
    }
    // https://mvnrepository.com/artifact/org.hibernate/hibernate-validator
    compile group: 'org.hibernate', name: 'hibernate-validator', version: '5.2.4.Final'
    

Версия Spring Загрузка в моем приложении - 1.2.7.