停止或重新部署时内存泄漏 - Spring 3.1.2,Hibernate 4.1.0,Spring Data-Jpa 1.1.0,Tomcat 7.0.30编辑 1问题配置问题
编辑 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(数据)Jpa凸起。我们还升级到了 Tomcat 7.0.39。CGLIB(之前没有提到,但被包括在内)也被提升到3.0
以下是我试图解决手头问题的一些事情,但没有运气:
- 更改了我们的 POM(使用 Maven),将数据库驱动程序设置为具有提供的范围
- 添加了对SLF4J的依赖关系,以包括7月到slf4j,因为有人提到这里可能存在潜在的泄漏
- 已试用类负载泄漏保护器 (https://github.com/mjiderhamn/classloader-leak-prevention)。这似乎已经奏效了(因为它在取消部署/关闭期间通过列出大量正在清理的东西来发送我的日志),但是在使用VisualVM之后,我的perm gen空间没有被回收。这可能对其他人有用...
- 尝试使用 Maven 的依赖关系分析工具列出 POM 中的所有排除项(在 IntelliJ 的 Maven 项目 窗口中,您可以右键单击依赖项,然后选择“显示依赖项”和 Shift-Delete 所有红色依赖项)。这没有帮助,但它将我的WAR文件大小减少了大约一MB左右。
-
根据Spring(https://jira.springsource.org/browse/SPR-9274)中未解决的错误报告,按如下方式重构了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; }
- 根据以下SO问题中 https://stackoverflow.com/a/15710827/941187 创建了一个“ThreadImmolator”:“这很可能在Tomcat中造成内存泄漏吗?这似乎与上面的TreadLocal泄漏检测几乎相同 - 我没有Tomcat检漏仪抱怨;然而,烫发空间在重新部署后仍未恢复。
- 我的第一次尝试是在我的WebConfig中添加一个@PreDestory(与@EnableWebMvc相同的bean),以尝试在关闭时触发它。烫发。Gen留下来了。
- 我的第二次尝试是子类化 ContextLoaderListener,覆盖 ContextDestoryed() 并内联函数。烫发。Gen留下来了。
- 由于ThreadImmolator没有帮助(或者似乎没有帮助),我尝试了 https://stackoverflow.com/a/16644476 中建议的解决方案:“如何清理线程局部”。这让我尝试:“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相关的所有类。我也尝试过搜索,我仍然可以看到它们在运行ThreadImmolator,Thread Leak Preventor和我上面尝试过的其他“重型解决方案”后转储后出现在堆中。org.springframework.core.NamedThreadLocal
也许上述信息可能会帮助某人,但我会继续用新的信息重新审视这个SO,或者如果我解决了这个问题。
问题
该应用程序在生产服务器上连续运行数天没有问题,但是当我执行更新部署时,Tomcat Manager程序将抱怨泄漏(如果我单击“查找泄漏”)。如果我执行 6-10 次部署,最终 Tomcat 内存不足,出现 PermGen 内存错误,我需要重新启动 Tomcat 服务,一切恢复正常。
当我在本地运行/调试应用程序并执行一些需要通过Jpa / Hibernate访问的操作(即,我从JpaRepository登录或请求List)然后关闭应用程序时,我在Tomcat的调试输出中收到以下消息:
Oct 03, 2012 2:55:13 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE: Web 应用程序 [/] 创建了一个 ThreadLocal,其键类型为 [org.springframework.core.NamedThreadLocal] (value [Transactional resources]) 和类型为 [java.util.HashMap] 的值 (值 [{public abstract java.lang.Object org.springframework.data.repository.CrudRepository.findOne(java.io.Serializable)=java.lang.Object@842e211}])但当 Web 应用程序被移除时,无法将其删除停止。随着时间的推移,线程将更新,以尝试避免可能的内存泄漏。
Oct 03, 2012 2:55:13 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE: Web 应用程序 [/] 创建了一个 ThreadLocal,其键类型为 [org.springframework.core.NamedThreadLocal] (value [Transactional resources]) 和一个类型为 [java.util.HashMap] 的值 (值 [{public abstract java.util.List org.springframework.data.jpa.repository.JpaRepository.findAll()=java.lang.Object@842e211}]),但在 Web 应用程序停止时未能将其删除。随着时间的推移,线程将更新,以尝试避免可能的内存泄漏。
Oct 03, 2012 2:55:13 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE: Web application [/] 创建了一个 ThreadLocal,其键类型为 [org.springframework.core.NamedThreadLocal] (value [Transactional resources]) 和类型为 [java.util.HashMap] 的值 (value [{public abstract java.lang.Iterable org.springframework.data.querydsl.QueryDslPredicateExecutor.findAll(com.mysema.query.types.Predicate)=java.lang.Object@842e211}]),但在以下情况下未能将其删除Web 应用程序已停止。随着时间的推移,线程将更新,以尝试避免可能的内存泄漏。
Oct 03, 2012 2:55:13 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE: Web 应用程序 [/] 创建了一个 ThreadLocal,其键类型为 [org.springframework.core.NamedThreadLocal] (value [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> {
}
我的所有仓库都是通过服务类访问的,该服务类在春季注册为@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>
问题
- 导致内存泄漏的原因是什么,我该如何解决?
- 我该如何调试此特定问题?
- 我的配置集中是否有任何可以改进的地方?
我真的没有解决这个问题的想法了。