コンテンツ・メディア第1事業部のjyukutyoこと阪田です。前回はコレクションのセッションについてのレポートでした。今回はクラスローダーのセッションについてレポートします。Leaksスライドはこちらにあります。https://static.rainfocus.com/oracle/oow16/sess/1461358415846001E0TZ/ppt/Classloader%20leaks%20public.pptx内容java.lang.OutOfMemoryError(OOME)はみな出会ったことがあるだろう。spaceとは出なくなったが、代わりにMetaspaceが出るようになった。ローカルでの開発時にも、継続的デプロイでもOOMEは出る。このセッションのアジェンダは次のようなものだ。OOMEは何を意味するのか?OOMEはなぜ起こるのか?どこでリークするのか?どのように避けるのか?OSSの例トレーニングOOMEは何を意味するのか?JVMのメモリはヒープとPermGen/Metaspaceとスタックである。スタックはスレッドごと、ローカル変数やメソッドのパラメータを持つ。ヒープはオブジェクトのインスタンスを持つ。PermGen/Metaspaceはjava.lang.Classインスタンスなどを持つ。EEとクラスのアンロードができる前に名付けられた。8でMetaspaceへ置き換えられた。space/Metaspaceは、あまりに多くのクラスをロードしたときに起こる。space/Metaspaceが起こる原因は次の2つが考えられる。アプリケーションが大きすぎる。7までは-XX:MaxPermSize=256Mなどと指定すればよい。8ではMetaspaceは自動的に増えていく。java.lang.Classインスタンスが再デプロイ後にガベージコレクトされない参照の型は次の4つがある。強い参照:到達可能なときは決してGCされないソフト参照:OOMEになる前にGCされる弱い参照(WeakHashMap):強い参照とソフト参照がないときにGCされるファントム参照:GCは妨げないGCの到達可能性は以下の図のようなことである。再デプロイでは、新しいWARファイルをデプロイすれば前のクラスローダはGCされる。クラスローダーの参照は次の図のようになる。どのようにリークが起きるのか。GCルートからクラスローダへ到達可能だと、クラスローダをGCすることができない。どこでリークするのか?Tomcatなどアプリケーションサーバのバグだ。Languageのバグといったこともある。GCルートでも起こる。システムクラスローダによってロードされるクラス。JDKのクラスにおけるstaticフィールドで起こる。ライブスレッドではローカル変数、メソッドのパラメータのスタック、java.lang.Threadインスタンスで起こる。introspectionのキャッシュ、シャットダウンフック、カスタムでデフォルトにしたAuthenticator、カスタムのセキュリティでのProvider、カスタムMBean、カスタムのThreadGroup、カスタムのプロパティエディタ...最初の呼び出し元でのcontextClassLoaderへの参照などで起こる。DriverManagerをもう少し詳しく見てみよう。MySQLのJDBCドライバを使うときはこうするだろう。Class.forName("com.mysql.jdbc.Driver");1Class.forName("com.mysql.jdbc.Driver");com.mysql.jdbc.Driverクラスにはstaticイニシャライザがある。driver!");}}registerDriver()メソッドは以下のようになっている。da));...このregisterDriversという変数は、以下のものだ。CopyOnWriteArrayList<>();図で表すと以下のようになる。そのため、コンテキストのシャットダウンで以下のように書かなければ、リークしてしまう。{DriverManager.deregisterDriver(...);}}スレッドもここで止めておかなければならない。次のようにスレッドを実装するのは悪いアイデアだ。something}}}止められるようには以下のようにする。false;}}このことは、次の記事に紹介されている。Spot次にThreadLocalについて考えてみよう。次のように実装してはならない。ThreadLocalのJavaDocには以下のようにある。...各スレッドは、スレッドが生存していてThreadLocalインスタンスがアクセス可能である間は、スレッド・ローカル変数のコピーへの暗黙的な参照を保持します。プールされたスレッドではスレッドはクラスローダより長生きする。ThreadLocalはThreadGlobalになる!ThreadLocalMapのJavaDocには以下のようにある。space(私訳)しかしながらリファレンスキューが使われないので、テーブルが容量不足になり始めたときだけ失効したエントリが削除されることが保証されます。参照を含んだカスタムの値は、staticなThreadLocalであればリークし、そうでなければ予測されていないGCとなる。カスタムのThreadLocalであればリークしない。ThreadLocalはクリアするようにしよう。{myThreadLocal.remove();}実際には、多くのOSSライブラリが違反している。どのように避けるのか?リーク防止のライブラリがある。実行時の依存は0で、設定は必要ない。Mavenであれば以下のように依存ライブラリを追加する。3.0以降用だが、2系のものもある。Validationの1.0.0.GAでは以下のようになっていた。List<ValidationProvider<?>>>();(私訳)List<ValidationProvider>はキーであるクラスローダに強い参照を持っている。JPAのCachingPersistenceProviderResolverと同じモデルを使うようにして。Validationの1.1.0.GAではこのクラスは次のようになっています。);とあるので、ソフト参照を使うようになったようです。JPAのCachingPersistenceProviderResolverクラスも調べてみました。PersistenceProvider>>>();その他のOSSjavax.el.BeanELResolverやorg.apache.cxf.transport.http.CXFAuthenticator、com.sun.star.lib.util.AsynchronousFinalizerも該当する。Analyzer(MAT)を使おう。http://www.eclipse.org/mat/次のオプションをつけてアプリケーションを実行しよう。最後に出てきた、MATについてはこのブログで私が記事を書いています。Analyzerで解析しよう!セッションとしては非常におもしろかったです。OSSの実例を挙げながらリークの原因を解説する箇所ではとても興奮しました。いう内容のセッションはJavaOneだと他にもいろいろとあります。