内存泄漏停止或重新部署 - Spring 3.1.2,Hibernate 4.1.0,Spring Data-Jpa 1.1.0,Tomcat 7.0.30

时间:2012-10-03 22:40:50

标签: java spring hibernate spring-mvc tomcat7

编辑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 Framework bump和Spring(Data)Jpa bump。我们也升级到Tomcat 7.0.39。 CGLIB(以前没有提到但是被包括在内)也被提升到3.0

以下是我试图解决手头问题但没有运气的一些事情:

  1. 更改了我们的POM(使用Maven)将我们的数据库驱动程序设置为提供其范围
  2. 为了包含jul-to-slf4j而增加了对SLF4J的依赖,因为有人提到可能存在潜在泄漏
  3. 尝试了Classload Leak Protector(https://github.com/mjiderhamn/classloader-leak-prevention)。这看起来很有效(因为它在取消部署/关闭期间通过列出大量正在清理的东西来发送我的日志)但是在使用VisualVM之后,我的perm gen空间没有被回收。这可能对其他人有用......
  4. 尝试使用Maven的依赖关系分析工具列出我的POM中的所有排除项(在Maven项目窗口的In IntelliJ中,您可以右键单击依赖项并选择“显示依赖关系”和“转移” - 删除所有红色依赖项)。这没有任何帮助,但它将我的WAR文件大小减少了大约MB左右。
  5. 根据Spring(https://jira.springsource.org/browse/SPR-9274)未解决的错误报告重构JPA Persistence配置(注意注释):

    @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;
    }
    
  6. 创建了一个&#39; ThreadImmolator&#39;根据{{​​3}}中的以下SO问题:&#39; https://stackoverflow.com/a/15710827/941187&#39;。这似乎与上面的TreadLocal泄漏检测几乎完全相同 - 我没有Tomcat泄漏检测器抱怨;但是,重新部署后,烫发空间仍未恢复。
    • 我的第一次尝试是在我的WebConfig中添加@PreDestory(具有@EnableWebMvc的相同bean)以尝试在关闭时触发它。烫发。 Gen仍然存在。
    • 我的第二次尝试是继承ContextLoaderListener,覆盖ContextDestoryed()并内联函数。烫发。 Gen仍然存在。
  7. 由于ThreadImmolator没有帮助(或似乎没有帮助),我尝试了Is this very likely to create a memory leak in Tomcat?中建议的解决方案,对以下SO问题:&#39; https://stackoverflow.com/a/16644476&#39; 。这导致我尝试:&#39; How to clean up threadlocals&#39;和&#39; https://weblogs.java.net/blog/jjviana/archive/2010/06/09/dealing-glassfish-301-memory-leak-or-threadlocal-thread-pool-bad-ide&#39;。
  8. 此时,我已经没有想法了。

    我也尝试过使用此作为起点(http://blog.igorminar.com/2009/03/identifying-threadlocal-memory-leaks-in.html)来学习堆分析。我可以找到没有清理的类加载器,我可以看到它仍然引用了所有与Spring相关的类。我也尝试过搜索org.springframework.core.NamedThreadLocal,在运行ThreadImmolator,Thread Leak Preventor和其他“重型解决方案”后,我仍然可以看到它们出现在堆中。 39;我在上面尝试过。

    也许以上信息可能会对某些人有所帮助,但我会继续用新信息重新审视这个SO,或者我是否解决了这个问题。

    问题

    应用程序在生产服务器上连续几天没有运行问题,但是当我执行部署更新时,Tomcat Manager程序会抱怨泄漏(如果我点击查找泄漏)。如果我执行6-10部署,最终Tomcat内存不足,出现PermGen内存错误,我需要重新启动Tomcat服务,一切恢复正常。

    当我在本地运行/调试应用程序并执行一些需要通过Jpa / Hibernate访问的操作(即,我登录或从JpaRepository请求List)然后关闭应用程序时,我在调试输出中收到以下消息来自Tomcat:

      

    2012年10月3日下午2:55:13 org.apache.catalina.loader.WebappClassLoader   checkThreadLocalMapForLeaks严重:Web应用程序[/]   使用类型的键创建了一个ThreadLocal   [org.springframework.core.NamedThreadLocal](值[Transactional   资源])和类型[java.util.HashMap]的值(值[{public   abstract java.lang.Object   org.springframework.data.repository.CrudRepository.findOne(java.io.Serializable接口)=java.lang.Object@842e211}])   但是在Web应用程序停止时无法将其删除。主题   随着时间的推移会更新,以避免可能的记忆   泄漏。

         

    2012年10月3日下午2:55:13   org.apache.catalina.loader.WebappClassLoader   checkThreadLocalMapForLeaks严重:Web应用程序[/]   使用类型的键创建了一个ThreadLocal   [org.springframework.core.NamedThreadLocal](值[Transactional   资源])和类型[java.util.HashMap]的值(值[{public   abstract java.util.List   org.springframework.data.jpa.repository.JpaRepository.findAll()= java.lang.Object@842e211}])   但是在Web应用程序停止时无法将其删除。主题   随着时间的推移会更新,以避免可能的记忆   泄漏。

         

    2012年10月3日下午2:55:13   org.apache.catalina.loader.WebappClassLoader   checkThreadLocalMapForLeaks严重:Web应用程序[/]   使用类型的键创建了一个ThreadLocal   [org.springframework.core.NamedThreadLocal](值[Transactional   资源])和类型[java.util.HashMap]的值(值[{public   abstract java.lang.Iterable   org.springframework.data.querydsl.QueryDslPredicateExecutor.findAll(com.mysema.query.types.Predicate)=java.lang.Object@842e211}])   但是在Web应用程序停止时无法将其删除。主题   随着时间的推移会更新,以避免可能的记忆   泄漏。

         

    2012年10月3日下午2:55:13   org.apache.catalina.loader.WebappClassLoader   checkThreadLocalMapForLeaks严重:Web应用程序[/]   使用类型的键创建了一个ThreadLocal   [org.springframework.core.NamedThreadLocal](值[Transactional   资源])和类型[java.util.HashMap]的值(值[{public   abstract data.domain.UserAccount   UserAccountRepository.findByUserName(java.lang.String中)=java.lang.Object@842e211}])   但是在Web应用程序停止时无法将其删除。主题   随着时间的推移会更新,以避免可能的记忆   泄漏。

    等等。

    配置

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

    我的所有存储库都是通过Service类访问的,该类在Spring中注册为@Component。这样做是为了从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>
    

    问题

    1. 导致内存泄漏的原因是什么?如何解决?
    2. 我将如何调试此特定问题?
    3. 我的配置集中有什么可以改进吗?
    4. 我真的没有解决这个问题的想法。

4 个答案:

答案 0 :(得分:4)

我认为你可能会同时发生两种泄密事件。 Spring警告你正常的“堆”内存泄漏。这还没有给你带来任何问题,因为......你的重新部署也导致了过多的PermGen使用,而这个问题首先打击了你。有关第二种泄漏的信息,请参阅Dealing with "java.lang.OutOfMemoryError: PermGen space" error [感谢duffymo]

[更新]

既然您说上述链接中的建议没有帮助,我能想到的唯一其他建议是:

  • 确保你的春豆在春天关闭时自行清理
  • 在构造函数或init方法中分配资源(任何不太可能被垃圾收集)的每个bean都应该有一个destroy方法来释放它
  • 对于引用spring-data模块中任何类的任何bean尤其如此,catalina抱怨此模块中的类
  • 增加你的permgen空间。即使这个建议没有解决问题,但是添加类似下面的内容会使这些失败的发生频率降低。
  

尝试-XX:MaxPermSize = 256m如果它仍然存在,请尝试-XX:MaxPermSize = 512m

最终的超级暴力方法是逐渐剥离应用程序,直到问题消失。这将帮助您缩小范围,以便您可以确定它是代码的问题还是Spring,Hibernate等中的错误

答案 1 :(得分:3)

slash3tc - 首先感谢伟大的写作,它帮助我在我的应用程序中插入了一些泄漏。我有完全相同的问题,结果是spring-data-jpa DAO 的调用没有活动事务。使用调度程序或@PostConstruct注释(以前称为InitializingBean)很容易发生这种情况。确保您已正确配置transaction management并且在活动事务下发生任何JPA调用(即通过使用@Transactional注释服务方法)。

编辑:似乎是(也是?)在1.4.3中修复的旧版spring-data-jpa版本的错误

更多详情:http://georgovassilis.blogspot.nl/2014/01/tomcat-spring-and-memory-leaks-when.html

答案 2 :(得分:1)

我有类似的问题,实际上有3个问题:

  1. 未发布JDBC驱动程序
  2. 没有被关联的连接
  3. 没有被释放的堆
  4. 他们每个人都有不同的解决方案:

    1. Moving mysql.jar to Tomcat/lib folder
    2. Manually killing the threads withholding connection in a listener
    3. 使用SPRING-JPA 1.3.4,因为1.3.4是修复错误的版本,但它不会改变SPRING-JPA模块的行为(下一个版本1.4.0引入了查询更新和“中断”现有代码)

答案 3 :(得分:0)

我遇到了类似的问题,我刚修好了。

  

[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);
}

除此之外,我还需要做以下事情:

  1. 从tomcat / libs中删除了PostgreSQL驱动程序。我已经在build.gradle中添加了依赖项,因此它们是重复的。
  2. 将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'
    
  3. 我的应用程序中的Spring Boot版本是1.2.7。