14

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

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

  1. 更改了我们的 POM(使用 Maven)以将我们的数据库驱动程序设置为具有所提供的范围
  2. 添加了对 SLF4J 的依赖以包含 jul-to-slf4j,因为这里提到可能存在潜在泄漏
  3. 试用了 Classload Leak Protector ( https://github.com/mjiderhamn/classloader-leak-prevention )。这似乎有效(因为它在取消部署/关闭期间通过列出它正在清理的大量内容向我的日志发送垃圾邮件)但是在使用 VisualVM 之后,我的 perm gen 空间没有被回收。这可能对其他人有用...
  4. 尝试使用 Maven 的依赖项分析工具列出我的 POM 中的所有排除项(在 Maven 项目窗口的 IntelliJ 中,您可以右键单击依赖项并选择“显示依赖项”并 Shift-Delete 所有红色依赖项)。这没有帮助,但它使我的 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. 在以下 SO 问题中根据https://stackoverflow.com/a/15710827/941187创建了一个“ThreadImmolator” :'这很可能会在 Tomcat 中造成内存泄漏吗?'。这似乎与上面的 TreadLocal 泄漏检测几乎相同——我没有 Tomcat 泄漏检测器抱怨;但是,perm gen 空间在重新部署后仍未恢复。
    • 我的第一次尝试是在我的 WebConfig(具有 @EnableWebMvc 的同一 bean)中添加一个 @PreDestory 以尝试在关闭时触发它。烫发。根留下了。
    • 我的第二次尝试是继承 ContextLoaderListener,覆盖 ContextDestoryed() 并内联函数。烫发。根留下了。
  7. 由于 ThreadImmolator 没有帮助(或似乎没有帮助),我在以下 SO 问题上尝试了https://stackoverflow.com/a/16644476中建议的解决方案:“如何清理 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 的调试输出中收到以下消息:

2012 年 10 月 3 日下午 2:55:13 org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks 严重:Web 应用程序 [/] 创建了一个 ThreadLocal,其键类型为 [org.springframework.core.NamedThreadLocal](值 [Transactional resources] ) 和 [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 resources] ) 和 [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 resources] ) 和 [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 resources] ) 和 [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> {
}

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

4

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

[更新]

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

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

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

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

于 2012-10-03T23:23:49.400 回答
3

slash3tc - 首先感谢您的精彩文章,它帮助我填补了我的应用程序中的一些漏洞。我遇到了完全相同的问题,结果证明是在没有活动事务的情况下调用 spring-data-jpa DAO。这很容易通过调度程序或 @PostConstruct 注释(以前称为 InitializingBean)发生。确保您已正确配置事务管理,并且任何 JPA 调用都发生在活动事务下(即通过使用 @Transactional 注释服务方法)。

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

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

于 2014-01-15T23:26:41.353 回答
1

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

  1. 未发布 JDBC 驱动程序
  2. 未释放连接
  3. 堆未释放

他们每个人都有不同的解决方案:

  1. 将 mysql.jar 移动到 Tomcat/lib 文件夹
  2. 在侦听器中手动终止线程保留连接
  3. 使用SPRING-JPA 1.3.4,因为 1.3.4 是修复错误的版本,但它不会改变 SPRING-JPA 模块的行为(下一个版本 1.4.0 引入了对查询的更新并“破坏”了现有代码)
于 2014-02-02T20:06:32.040 回答
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'
    

我的应用程序中的 Spring Boot 版本是 1.2.7。

于 2016-07-05T06:03:41.267 回答