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 ファイルのサイズが約 1 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 を追加して、閉じるときにトリガーしようとすることでした。パーマ。ゲンは残った。
    • 私の 2 番目の試みは、ContextLoaderListener をサブクラス化し、ContextDestoryed() をオーバーライドして、関数をインライン化することでした。パーマ。ゲンは残った。
  7. ThreadImmolator が役に立たなかった (または役に立たないように見えた) ため、https ://stackoverflow.com/a/16644476 で提案されている解決策を次の SO の質問で試しました:「threadlocals をクリーンアップする方法」。これにより、次のことを試すようになりました: ' および ' 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 Manager プログラムがリークについて文句を言います ([リークを検索] をクリックすると)。6 ~ 10 回のデプロイを実行すると、最終的に Tomcat は PermGen メモリ エラーでメモリ不足になり、Tomcat サービスを再起動する必要があり、すべてが正常に戻ります。

アプリケーションをローカルで実行/デバッグし、Jpa/Hibernate を介したアクセスを必要とするいくつかのアクション (つまり、JpaRepository からのログインまたはリストの要求) を実行してからアプリケーションをシャットダウンすると、Tomcat からのデバッグ出力に次のメッセージが表示されます。

2012 年 10 月 3 日 2:55:13 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE: Web アプリケーション [/] は、タイプ [org.springframework.core.NamedThreadLocal] のキーを持つ ThreadLocal を作成しました (値 [トランザクション リソース] ) および [java.util.HashMap] 型の値 (value [{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 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE: Web アプリケーション [/] は、タイプ [org.springframework.core.NamedThreadLocal] のキーを持つ ThreadLocal を作成しました (値 [トランザクション リソース] ) および [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 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE: Web アプリケーション [/] は、タイプ [org.springframework.core.NamedThreadLocal] のキーを持つ ThreadLocal を作成しました (値 [トランザクション リソース] ) および [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 アプリケーションが停止したときに削除できませんでした。スレッドは、メモリ リークの可能性を回避するために、時間の経過とともに更新されます。

2012 年 10 月 3 日 2:55:13 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks SEVERE: Web アプリケーション [/] は、タイプ [org.springframework.core.NamedThreadLocal] のキーを持つ ThreadLocal を作成しました (値 [トランザクション リソース] ) およびタイプ [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

同時に 2 種類のリークが発生している可能性があると思います。Spring は、通常の「ヒープ」メモリ リークについて警告しています。これはまだあなたに問題を引き起こしていません.なぜなら...あなたの再配備は過剰な PermGen の使用も引き起こしているからです.そしてこの問題が最初にあなたを襲っています. 2 番目の種類のリークについては、「java.lang.OutOfMemoryError: PermGen space」エラーの処理を参照してください [Thanks duffymo]

[アップデート]

上記のリンクの提案は役に立たなかったと言うので、私が考えることができる唯一の他の提案は次のとおりです。

  • 春がシャットダウンしたときに春の豆がきれいになっていることを確認してください
  • コンストラクターまたは init メソッドでリソース (ガベージ コレクションの可能性が低いもの) を割り当てるすべての Bean には、割り当てを解除するための destroy メソッドが必要です。
  • これは、spring-data モジュール内の任意のクラスを参照する Bean に特に当てはまります。カタリナは、このモジュール内のクラスについて不平を言っています
  • permgen スペースを増やします。この提案で問題が解決されなかったとしても、次のようなものを追加すると、これらの障害の発生頻度が低下するはずです。

-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 に答える
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 に答える