java - 停止或重新部署时出现内存泄漏 - Spring 3.1.2、Hibernate 4.1.0、Spring Data-Jpa 1.1.0、Tomcat 7.0.30

标签 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 凹凸和 Spring (Data) Jpa 凹凸。我们还升级到了 Tomcat 7.0.39。 CGLIB(之前没有提到但被包括在内)也被提升到 3.0

以下是我在没有运气的情况下尝试解决手头问题的一些方法:
  • 更改我们的 POM(使用 Maven)以将我们的数据库驱动程序设置为其提供的范围
  • 添加了对 SLF4J 的依赖以包含 jul-to-slf4j,因为有人提到这里可能存在潜在泄漏
  • 试用了类负载泄漏保护器 ( https://github.com/mjiderhamn/classloader-leak-prevention )。这似乎有效(因为它在取消部署/关闭期间通过列出大量正在清理的内容来垃圾邮件发送我的日志)但是在使用 VisualVM 后,我的永久空间没有被回收。这可能对其他人有用...
  • 尝试使用 Maven 的依赖项分析工具列出我的 POM 中的所有排除项(在 Maven 项目窗口的 IntelliJ 中,您可以右键单击依赖项并选择“显示依赖项”并按住 Shift 键删除所有红色依赖项)。这没有帮助,但它使我的 WAR 文件大小减少了大约 1 MB。
  • 根据 Spring ( https://jira.springsource.org/browse/SPR-9274 ) Unresolved 错误报告,重构 JPA 持久性配置如下(注意注释):
    @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;
    }
    
  • 按照 https://stackoverflow.com/a/15710827/941187 创建了一个“ThreadImmolator”在以下 SO 问题中:' Is this very likely to create a memory leak in Tomcat? '。这似乎与上面的 TreadLocal 泄漏检测几乎相同——我没有提示 Tomcat 泄漏检测器;但是,重新部署后仍然没有恢复 perm gen 空间。
  • 我的第一次尝试是在我的 WebConfig(具有 @EnableWebMvc 的同一 bean)中添加一个 @PreDestory 以尝试在关闭时触发它。烫发。根留下了。
  • 我的第二次尝试是对 ContextLoaderListener 进行子类化,覆盖 ContextDestoryed() 并内联函数。烫发。根留下了。
  • 由于 ThreadImmolator 没有帮助(或似乎没有帮助),我尝试了 https://stackoverflow.com/a/16644476 中建议的解决方案关于以下 SO 问题:' How to clean up threadlocals '。这让我尝试:' https://weblogs.java.net/blog/jjviana/archive/2010/06/09/dealing-glassfish-301-memory-leak-or-threadlocal-thread-pool-bad-ide ' 和 ' http://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 管理器程序会提示泄漏(如果我单击查找泄漏)。如果我执行 6-10 次部署,最终 Tomcat 会因 PermGen 内存错误而耗尽内存,我需要重新启动 Tomcat 服务,一切都会恢复正常。

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

    Oct 03, 2012 2:55:13 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE: The web application [/] created a ThreadLocal with key of type [org.springframework.core.NamedThreadLocal] (value [Transactional resources]) and a value of type [java.util.HashMap] (value [{public abstract java.lang.Object org.springframework.data.repository.CrudRepository.findOne(java.io.Serializable)=java.lang.Object@842e211}]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.

    Oct 03, 2012 2:55:13 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE: The web application [/] created a ThreadLocal with key of type [org.springframework.core.NamedThreadLocal] (value [Transactional resources]) and a value of type [java.util.HashMap] (value [{public abstract java.util.List org.springframework.data.jpa.repository.JpaRepository.findAll()=java.lang.Object@842e211}]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.

    Oct 03, 2012 2:55:13 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE: The web application [/] created a ThreadLocal with key of type [org.springframework.core.NamedThreadLocal] (value [Transactional resources]) and a value of type [java.util.HashMap] (value [{public abstract java.lang.Iterable org.springframework.data.querydsl.QueryDslPredicateExecutor.findAll(com.mysema.query.types.Predicate)=java.lang.Object@842e211}]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.

    Oct 03, 2012 2:55:13 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE: The web application [/] created a ThreadLocal with key of type [org.springframework.core.NamedThreadLocal] (value [Transactional resources]) and a value of type [java.util.HashMap] (value [{public abstract data.domain.UserAccount UserAccountRepository.findByUserName(java.lang.String)=java.lang.Object@842e211}]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.



    等等等等。

    配置

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

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

    问题
  • 是什么导致了内存泄漏以及如何修复它?
  • 我将如何调试这个特定问题?
  • 我的配置集中有什么可以改进的吗?

  • 我真的没有解决这个问题的想法。

    最佳答案

    我认为您可能同时发生两种泄漏。 Spring 警告您有关正常的“堆”内存泄漏。这还没有给你带来问题,因为......你的重新部署也导致了永久代的过度使用,这个问题首先袭击了你。有关第二种泄漏的信息,请参阅 Dealing with "java.lang.OutOfMemoryError: PermGen space" error [感谢达菲莫]

    [更新]

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

  • 确保您的 Spring Bean 在 Spring 关闭时自我清理
  • 每个在构造函数或 init 方法中分配资源(任何不太可能被垃圾收集的东西)的 bean 都应该有一个 destroy 方法来释放它
  • 对于引用 spring-data 模块中任何类的任何 bean 尤其如此,catalina 提示此模块中的类
  • 增加您的永久空间。即使这个建议没有解决问题,添加如下内容也应该可以减少这些故障的发生。

  • Try -XX:MaxPermSize=256m and if it persists, try -XX:MaxPermSize=512m



    终极的 super 暴力方法是逐渐从你的应用程序中删除一些东西,直到问题消失。这将帮助您将范围缩小到可以确定它是您的代码问题还是 Spring、Hibernate 等中的错误的程度

    关于java - 停止或重新部署时出现内存泄漏 - Spring 3.1.2、Hibernate 4.1.0、Spring Data-Jpa 1.1.0、Tomcat 7.0.30,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12717895/

    相关文章:

    java - 将@Configuration文件指向hbm.xml

    java - 绘制动态图

    java - Swing JDialog 的内容 Pane 在执行幻灯片时未更新

    java - Spring 批处理 3,IBM JVM (BackToBackPatternClassifier) 上的配置加载时出错

    java - 如何在 JDL-Studio 中为 String 指定 MySQL 数据类型

    java - Eclipse/Hibernate 工具错误 : Archive classpath entry doesn't exist

    java - 找出加载类的数量

    java - 密码检查成功后加载新网页

    java - Spring Transaction - noRollbackFor 在发生异常时不会提交

    java - 如何在多对多关系中使用 hibernate 和 JPA 删除孤立实体?