Upload
norito-agetsuma
View
1.980
Download
17
Embed Size (px)
Citation preview
Tomcatの実装から学ぶClassLoaderLeak
@n-agetsu
上妻宜人 (あげつまのりと)
第十回 #渋谷java
第十回 #渋谷java
あげつまのりと
• SIer勤務
• Javaトラブルシューティング
• JBoss, Tomcat社内サポート
• はてな見習いプログラミング日記
• Software Design 2014 10月号寄稿
第十回 #渋谷java
ClassLoaderLeakって?
第十回 #渋谷java
ホットデプロイ時に古いクラスローダがGCされない困った不具合です
(〜JDK7) java.lang.OutOfMemoryError : Perm Gen
(JDK8〜) Metaspaceの増大/OutOfMemoryError(-XX:MaxMetaSpaceSize設定時)
第十回 #渋谷java
ホットデプロイの普及により遭遇率があがっています
OOM!.war
deploy
deploy
deploy
.war
第十回 #渋谷javahttp://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Edit-clear_mirrored.svg/120px-Edit-clear_mirrored.svg.png
【NEW】test.war
【OLD】test.war
TomcatはClassLoaderLeakの検知・解放機能を実装しています。
参考: O’Reilly Japan 詳解Tomcat
第十回 #渋谷java
Tomcat リーク検知実装コードを読む(org.apache.catalina.loader.WebappClassLoaderBase)
何をするとリークするかわかる
第十回 #渋谷java
Commonクラスローダ
Webappクラスローダ
(稼働中WAR)
アンデプロイ済Webapp
クラスローダ
ClassLoaderLeakの主な原因
Bootstrapクラスローダ
Systemクラスローダ Tomcatスレッドプール
1. 上位クラスローダで読み込まれたクラスからの強参照
例 : java.sql.DriverManagerのフィールド変数⇒WEB-INF/libのJDBCドライバ など
2. プール内スレッドからの参照・ スタック中スレッドが旧APクラスを参照・ ThredLocal.remove() の漏れ
Tomcat8のデフォルトのクラスローダ階層
(クラスローダについては以下参照)org.apache.catalina.startup.Bootstrap.initメソッドとorg.apache.catalina.loader.WebappLoaderクラス
第十回 #渋谷java
Commonクラスローダ
Webappクラスローダ
(稼働中WAR)
アンデプロイ済Webapp
クラスローダ
ClassLoaderLeakの主な原因
Bootstrapクラスローダ
Systemクラスローダ Tomcatスレッドプール
1. 上位クラスローダで読み込まれたクラスからの強参照
例 : java.sql.DriverManagerのフィールド変数⇒WEB-INF/libのJDBCドライバ など
2. プール内スレッドからの参照・ スタック中スレッドが旧APクラスを参照・ ThredLocal.remove() の漏れ
Tomcat8のデフォルトのクラスローダ階層
(クラスローダについては以下参照)org.apache.catalina.startup.Bootstrap.initメソッドとorg.apache.catalina.loader.WebappLoaderクラス
JDKクラス、Tomcatスレッドプール などアンデプロイしても使われるクラスから、
アンデプロイ済APへの参照が残るのが共通点
第十回 #渋谷java
Tomcatが検知するClassLoaderLeak
• java.lang.ThreadLocal.remove 漏れ
• java.sql.DriverManager.deregisterDriver 漏れ
• その他
• RMI関連 sun.rmi.transport.ObjectTable のobjTable, implTable クリア漏れ
• 実行中スレッドによる参照
• java.beans.Introspector.flushCaches()の実行漏れ
• Commons HttpClientの keep alive 用スレッドが残り続ける
• java.util.Timer.cancel() キャンセル漏れ
• 古いJVM?のGCバグ static / final 変数解放漏れ (JDK7, 8では再現せず)
org.apache.catalina.loader.WebappClassLoaderBase クラスを読んでみると..
第十回 #渋谷java
ThreadLocal.remove 実行漏れ
public class UserThreadContext {
private static ThreadLocal<User> context = ...;
public static void setUser(User user) {
userContext.set(user);
}
}
掃除されてないスレッドローカル
第十回 #渋谷java
ThreadLocal.remove 実行漏れ
public class UserThreadContext {
private static ThreadLocal<User> context = ...;
public static void setUser(User user) {
userContext.set(user);
}
// filterとかinterceptorとかでリクエスト終了までに実行public static void cleanup() {
userContext.remove();
}
}
データは入れたら消す。cleanupコードの追加。
第十回 #渋谷java
なぜremove漏れでリークする?
ThreadLocalにユーザ情報を追加
HTTPリクエスト
Tomcatスレッドプール
ThreadLocalMap
Entry
User
プールに戻ったスレッドが持つThreadLocalMapからアプリケーションへの参照が残り続ける
ThreadLocalMap
Entry
User
第十回 #渋谷java
なぜremove漏れでリークする?
ThreadLocalにユーザ情報を追加
HTTPリクエスト
Tomcatスレッドプール
ThreadLocalMap
Entry
UserTomcatの場合、アンデプロイ時にプールのチェック・リフレッシュにより、ThreadLocalによるリークを検知・解放
解放: ThreadPoolExecutor.setCorePoolSize(0);
(プースサイズを0にしてidleを全て解放)
第十回 #渋谷java
DriverManager.deregisterDriver 漏れ
.war のWEB-INF/libにJDBCドライバを含めて以下コードで再現
@WebServlet(“/leak”)
public class LeakServlet extends HttpServlet {
@Override
public void doGet(...) {
try {Class.forName(“org.postgresql.Driver”);
} catch (ClassNot.. e) { ... }
}
}
第十回 #渋谷java
JDBCクラスロード時にDriverManagerに登録
import org.postgresql;
public class Driver implements java.sql.Driver {
static {try {
java.sql.DriverManager.registerDriver(new Driver());....
Common
Webapp UndeployedWEB-INF/lib/xxxJDBC.jar
Bootstrap
System
DriverManager
• .warにJDBCに含めた場合、参照が残る。
• tomcat/libに置いた場合は、ドライバがCommonでクラスロードされるので残らない。
第十回 #渋谷java
DriverManagerによるリークの対処
• JDBCドライバを.warに含めない
• ServletContextListenerでAP終了時に解放
• java.sql.DriverManager.deregisterDriver実行
• Tomcat8ではデフォルトで検知・解放が有効
• JREMemoryLeakPreventionListenerで解放される
第十回 #渋谷java
どうやってClassLoaderLeakを解析するか?
第十回 #渋谷java
ClassLoaderLeakの見つけ方
1. HeapDump取得jcmd <pid> GC.heapdump filename=...
2. Eclipse Memory Analyzerロードしたら “Duplicate Classes”
第十回 #渋谷java
ClassLoaderLeakの見つけ方
同じクラスが複数のWebappClassLoaderからロードされていればClassLoaderLeakの可能性大
第十回 #渋谷java
Path To GC Roots で原因を特定
TomcatのTaskThreadが持つThreadLocalからの参照が原因
GCして欲しいWebappClassLoaderを選択してGCルートをチェック
第十回 #渋谷java
ライブラリによるClassLoaderLeak
• 伝統的なライブラリにあるClassLoaderLeak
• Commons Logging 1.0.4 以前
• iBATIS 2.3.4 以前 (iBATIS-540)
• log4j 1.2.16 以前 (Bug 50486, MDC利用時のみ)
• その他
• ClassLoader, ThreadLocal, HotDeploy Leak で検索
第十回 #渋谷java
ClassLoaderLeakはTomcatだけではない
• WebLogicServer やWildFlyでも起こり得る
(JVM起動中に動的にクラスローダの生成・破棄があれば起こる可能性有)
• 個人的にはWebLogicServerで何度か遭遇
• プロダクション再デプロイメントの使用を意図
• WebLogicは悪くない, 前述の AP or ライブラリ起因でリーク
第十回 #渋谷java
まとめ
• ClassLoaderLeakはホットデプロイにより顕在化
• Tomcatは色々なClassLoaderLeakの検知・解放が可能
• 原因はThreadLocal解放漏れを筆頭に多種多様
• ClassLoaderLeakは怖くない
• ヒープダンプより比較的簡単に原因特定が可能
• Duplicate Class => Path to GC Roots