クラスローダ
  リークパターン
なにそれおいしいの?
                           Takayoshi Kimura

Senior Software Maintenance Engineer, JBoss
                      Global Support Service
                                    Red Hat
自己紹介
nekop
● JBoss の中の人
● Emacs で Java 書く人


● オープンソース大好きっ子


● ネコよりイヌが好き
宣伝
JBoss では
エンジニアを
  募集して
 おります!
JBoss で働きませんか
● Java とオープンソースが好き
  な人におすすめ
● おやつ無料


● 飲み物自動販売機無料
JBoss で働きませんか
● 主に「オープンソースで遊ぶ」
  のと「技術的な人助け」
● Linux カーネルから Java アー


  キテクトまで豊富な同僚
 本題 
クラスローダ
  リーク
何が起こる?
● java.lang.OutOfMemoryError:
  perm gen space
● クラス定義情報が置いてあるメ


  モリ領域 (Perm) を消費しっぱ
  なし
いつリークする?
● クラスローダが破棄、再作成さ
  れるタイミング
● 具体例として、アプリケーショ


  ンサーバに再デプロイを行った
  とき
なぜリークする?
● そのクラスローダへの参照が
  残ってるから
● そのクラスローダからロードさ


  れたクラスへの参照が残ってる
  から
● クラスローダをまたがる参照
パターン
   その1
ThreadLocal
ThreadLocal
● 同一スレッド上で値の受け渡し
  ができて便利
● フレームワークなどでよく使わ


  れている
ThreadLocal の参照構造
● ThreadLocal に設定した値は
  Thread から強参照される
● 詳しく知りたい人はソース嫁
ThreadLocal 開放条件
● ThreadLocal へ到達可能な強参
  照が一つもない場合開放される
● Thread が開放されたら一緒に


  開放される
ありがちなリークコード
public class ThreadLocalKeyValue {

    private ThreadLocal tl = new ThreadLocal();

    public void add(Object key, Object value) {
        tl.set(new KeyValuePair(key, value));
    }

    public Object getKey() {
        return ((KeyValuePair)tl.get()).key;
    }

    public Object getValue() {
        return ((KeyValuePair)tl.get()).value;
    }

    private class KeyValuePair {
        public Object key, value;
        public KeyValuePair(Object key, Object value) {
            this.key = key; this.value = value;
        }
    }
}
テスト
ThreadLocalKeyValue target = new ThreadLocalKeyValue();
ReferenceQueue queue = new ReferenceQueue();
PhantomReference ref = new PhantomReference(target, queue);

target.add("foo", "bar");
target = null;

System.gc();
System.out.println("Released?: " + ref.isEnqueued());
テスト結果
false って何よ
   ( ゚Д゚)
修正
public class ThreadLocalKeyValue {

    private ThreadLocal tl = new ThreadLocal();

    public void add(Object key, Object value) {
        tl.set(new KeyValuePair(key, value));
    }

    public Object getKey() {
        return ((KeyValuePair)tl.get()).key;
    }

    public Object getValue() {
        return ((KeyValuePair)tl.get()).value;
    }

    private static class KeyValuePair {
        public Object key, value;
        public KeyValuePair(Object key, Object value) {
            this.key = key; this.value = value;
        }
    }
}
テスト結果
 true
ハァ ?
( ゚Д゚)
ThreadLocal まとめ

●   設定した値は Thread から強参照される
●   ThreadLocal に渡す「値」の参照関係に注意
●   スレッドのコントロールが自分に無い環境では
    Thread の開放に伴う ThreadLocal の開放は
    期待できない
    –   アプリケーションサーバ上では当然スレッドは
        プールされている
    –   リークしないのは「入れたら確実に消す ( 一時的な
        利用 ) 」か「入れっぱなしでも値の参照関係が安
        全だと保証できる」場合のみ
ThreadLocal と Tomcat

●   Tomcat はアンデプロイ時に ThreadLocal を
    リフレクションで開放してくれる機能がある
    –   アプリケーションやフレームワーク、ライブラリ
        のクラスローダリークバグを隠してしまういやん
        な機能
    –   心当たりのあるフレームワーク / ライブラリ開発者
        さんは直してください!
●   でもその機能にスレッドセーフじゃないバグが
    あるので 6.0.27 以降は無効になっている
    –   https://issues.apache.org/bugzilla/show_bug.
        cgi?id=48895
パターン
  その2
java.util.
 logging
java.util.logging の Level
● Level 継承するとクラスローダ
  がリークする
● ここ嫁

    ●   http://blogs.sun.com/fkieviet/entry/cl
        assloader_leaks_the_dreaded_java
パターン
  その 3
  既成
ライブラリ
既成ライブラリ
●   元々複数のクラスローダ上で動作することを想
    定していないライブラリ
    –   ContextClassLoader や static フィールドの誤用
    –   ThreadLocal や WeakHashMap などの参照構造
         の理解不足
既成ライブラリ
●   Commons-logging
    –   だいぶ前に一通り直ったけど、利用する側のコー
        ドによってはリークする
●   Commons-beanutils
    –   1.8.0 時点で一応メジャーな問題は全部解決してい
        る
    –   https://issues.apache.org/jira/browse/BEANU
        TILS-291
●   他にもいっぱいあるよ!
ClassLoader Leak Patterns

ClassLoader Leak Patterns