提问者:小点点

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


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

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

@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;
}
  • 我的第一次尝试是在我的WebConfig中添加一个@PreDestory(具有@EnableWebMvc的同一bean)以尝试在关闭时触发它。烫发。Gen仍然存在。
  • 我的第二次尝试是对ContextLoaderListener进行子类化,覆盖ContextDestored()并内联函数。烫发。Gen仍然存在。

在这一点上,我已经没有想法了。

我还尝试以此作为起点(http://cdivilly.wordpress.com/2012/04/23/permgen-memory-leak/)学习Heap分析。我可以找到未清理的类加载器,并且我可以看到它仍然引用与Spring相关的所有类。我还尝试搜索org. springframe.core.NamedThreadLocal,并且我仍然可以看到它们在运行ThreadImmolator、Thread Leak Preventor和我上面尝试的其他“重手解决方案”后转储后显示在堆中。

也许上述信息可能对某人有所帮助,但如果我解决了这个问题,我会继续用新信息重新审视这个SO。

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

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

Oct 03,2012 2:55:13PMorg.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE:Web应用程序[/]创建了一个ThreadLocal,其密钥类型为[org.springframe.core.NamedThreadLocal](值[Transactional Resources]),值类型为[java.util.HashMap](值[{公共抽象java.lang.Objectorg.springframework.data.reposity.CrudReposity.findOne(java.io.Serializable)=java.lang.Object@842e211}]),但在Web应用程序停止时未能将其删除。线程将随着时间的推移而更新,以避免可能的内存泄漏。

Oct 03,2012 2:55:13PMorg.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE:Web应用程序[/]创建了一个ThreadLocal,其密钥类型为[org.springframe.core.NamedThreadLocal](值[Transactional Resources]),值类型为[java.util.HashMap](值[{公共抽象java.util.Listorg.springframework.data.jpa.reposity.JpaReposory.findAll()=java.lang.Object@842e211}]),但在Web应用程序停止时未能将其删除。线程将随着时间的推移而更新,以避免可能的内存泄漏。

Oct 03,2012 2:55:13PMorg.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE:Web应用程序[/]创建了一个ThreadLocal,其密钥类型为[org.springframe.core.NamedThreadLocal](值[Transactional Resources]),值类型为[java.util.HashMap](值[{公共抽象java.lang.Iterableorg.springframework.data.querydsl.QueryDslPredicateExecutor.findAll(com.mysema.query.type.Predicate)=java.lang.Object@842e211}]),但在Web应用程序停止时未能将其删除。线程将随着时间的推移而更新,以避免可能的内存泄漏。

Oct 03,2012 2:55:13PMorg.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE:Web应用程序[/]创建了一个ThreadLocal,其键类型为[org.springframe.core.NamedThreadLocal](值[Transactional Resources]),值类型为[java.util.HashMap](值[{公共抽象数据.domain.UserAccount UserAccount tRepository.findByUserName(java.lang.String)=java.lang.Object@842e211}]),但在Web应用程序停止时未能将其删除。线程将随着时间的推移而更新,以避免可能的内存泄漏。

等等,等等。

Spring是通过注释配置的,我还使用Postgres 8.4作为数据库后端。

JPA是通过注释配置的(jpa-reposcity-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. 我的配置集中有什么可以改进的吗?

我真的没有办法解决这个问题了。


共3个答案

匿名用户

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

[更新]

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

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

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

最终的超级暴力方法是逐渐将问题从应用程序中剥离出来,直到问题消失。这将帮助您将范围缩小到可以识别是代码问题还是Spring、Hibernate等中的bug

匿名用户

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

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

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

匿名用户

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

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

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

  1. 将mysql. jar移动到Tomcat/lib文件夹
  2. 在侦听器中手动终止保留连接的线程
  3. 使用SPRING-JPA 1.3.4,原因1.3.4是修复bug的版本,但它不会改变SPRING-JPA模块的行为(下一个版本1.4.0引入了对查询的更新并“破坏”了现有代码)