tomcatの実装から学ぶクラスローダリーク #渋谷java
TRANSCRIPT
![Page 1: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/1.jpg)
Tomcatの実装から学ぶClassLoaderLeak
@n-agetsu
上妻宜人 (あげつまのりと)
第十回 #渋谷java
![Page 2: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/2.jpg)
第十回 #渋谷java
あげつまのりと
• SIer勤務
• Javaトラブルシューティング
• JBoss, Tomcat社内サポート
• はてな見習いプログラミング日記
• Software Design 2014 10月号寄稿
![Page 3: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/3.jpg)
第十回 #渋谷java
ClassLoaderLeakって?
![Page 4: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/4.jpg)
第十回 #渋谷java
ホットデプロイ時に古いクラスローダがGCされない困った不具合です
(〜JDK7) java.lang.OutOfMemoryError : Perm Gen
(JDK8〜) Metaspaceの増大/OutOfMemoryError(-XX:MaxMetaSpaceSize設定時)
![Page 5: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/5.jpg)
第十回 #渋谷java
ホットデプロイの普及により遭遇率があがっています
OOM!.war
deploy
deploy
deploy
.war
![Page 6: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/6.jpg)
第十回 #渋谷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
![Page 7: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/7.jpg)
第十回 #渋谷java
Tomcat リーク検知実装コードを読む(org.apache.catalina.loader.WebappClassLoaderBase)
何をするとリークするかわかる
![Page 8: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/8.jpg)
第十回 #渋谷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クラス
![Page 9: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/9.jpg)
第十回 #渋谷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への参照が残るのが共通点
![Page 10: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/10.jpg)
第十回 #渋谷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 クラスを読んでみると..
![Page 11: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/11.jpg)
第十回 #渋谷java
ThreadLocal.remove 実行漏れ
public class UserThreadContext {
private static ThreadLocal<User> context = ...;
public static void setUser(User user) {
userContext.set(user);
}
}
掃除されてないスレッドローカル
![Page 12: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/12.jpg)
第十回 #渋谷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コードの追加。
![Page 13: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/13.jpg)
第十回 #渋谷java
なぜremove漏れでリークする?
ThreadLocalにユーザ情報を追加
HTTPリクエスト
Tomcatスレッドプール
ThreadLocalMap
Entry
User
プールに戻ったスレッドが持つThreadLocalMapからアプリケーションへの参照が残り続ける
ThreadLocalMap
Entry
User
![Page 14: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/14.jpg)
第十回 #渋谷java
なぜremove漏れでリークする?
ThreadLocalにユーザ情報を追加
HTTPリクエスト
Tomcatスレッドプール
ThreadLocalMap
Entry
UserTomcatの場合、アンデプロイ時にプールのチェック・リフレッシュにより、ThreadLocalによるリークを検知・解放
解放: ThreadPoolExecutor.setCorePoolSize(0);
(プースサイズを0にしてidleを全て解放)
![Page 15: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/15.jpg)
第十回 #渋谷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) { ... }
}
}
![Page 16: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/16.jpg)
第十回 #渋谷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でクラスロードされるので残らない。
![Page 17: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/17.jpg)
第十回 #渋谷java
DriverManagerによるリークの対処
• JDBCドライバを.warに含めない
• ServletContextListenerでAP終了時に解放
• java.sql.DriverManager.deregisterDriver実行
• Tomcat8ではデフォルトで検知・解放が有効
• JREMemoryLeakPreventionListenerで解放される
![Page 18: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/18.jpg)
第十回 #渋谷java
どうやってClassLoaderLeakを解析するか?
![Page 19: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/19.jpg)
第十回 #渋谷java
ClassLoaderLeakの見つけ方
1. HeapDump取得jcmd <pid> GC.heapdump filename=...
2. Eclipse Memory Analyzerロードしたら “Duplicate Classes”
![Page 20: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/20.jpg)
第十回 #渋谷java
ClassLoaderLeakの見つけ方
同じクラスが複数のWebappClassLoaderからロードされていればClassLoaderLeakの可能性大
![Page 21: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/21.jpg)
第十回 #渋谷java
Path To GC Roots で原因を特定
TomcatのTaskThreadが持つThreadLocalからの参照が原因
GCして欲しいWebappClassLoaderを選択してGCルートをチェック
![Page 22: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/22.jpg)
第十回 #渋谷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 で検索
![Page 23: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/23.jpg)
第十回 #渋谷java
ClassLoaderLeakはTomcatだけではない
• WebLogicServer やWildFlyでも起こり得る
(JVM起動中に動的にクラスローダの生成・破棄があれば起こる可能性有)
• 個人的にはWebLogicServerで何度か遭遇
• プロダクション再デプロイメントの使用を意図
• WebLogicは悪くない, 前述の AP or ライブラリ起因でリーク
![Page 24: Tomcatの実装から学ぶクラスローダリーク #渋谷Java](https://reader036.vdocuments.pub/reader036/viewer/2022081419/55a523a11a28ab50018b4569/html5/thumbnails/24.jpg)
第十回 #渋谷java
まとめ
• ClassLoaderLeakはホットデプロイにより顕在化
• Tomcatは色々なClassLoaderLeakの検知・解放が可能
• 原因はThreadLocal解放漏れを筆頭に多種多様
• ClassLoaderLeakは怖くない
• ヒープダンプより比較的簡単に原因特定が可能
• Duplicate Class => Path to GC Roots