16 July 2005

Blog - The art of .war 有一篇關於 web app Out of memory 的討論。裡頭詳述為何 web app 在數次 restart 之後,會發生 memory leaking。總結一句:問題出現在有若干的程式 hold reference of ClassLoader,以致於 web container restart web app 後,沒辦法將前一個 web app 的 classloader 給 GC (garbage collect) 掉,因此 restart web app 幾次之後,便出現 OOM (out of memory)。其中比較特別原因是 ThreadLocal...

ThreadLocal 對 web app 來說非常的便利 (有用過的 HibernateUtil 都知道)... class 可以在不同 thread 裡存不同的物件,因此輕易可以做到 Thread-safe。然而 web container 通常都會替 Thread 做 pooling,也就是說 Thread 即使用完了,container 會回收重用,不會馬上消失。如果你放在 ThreadLocal 裡的物件 無法、或是很難被 GC 掉 那麼那個 Thread 就會活的很久,或甚至永遠不死,直到你關閉 container 為止。而由於 ThreadLocal 的內部實作與應用方式,會造成 ThreadLocal 直接/間接的 reference 到 ClassLoader。(詳細討論請見 ThreadLocals and memory leaks revisited) 這下可慘啦,restart web app 後,前一個 web app 用到的 Thread 還硬活在 thread pool 裡,而那個thread reference 了上一個 web app 的 ClassLoader,因此上一個 ClassLoader 無法被 GC 掉,多來個幾次 web container 就跨了.....

要解決這個問題,首先要做的就是適時的將 threadLocal 裡放的物件清除掉:



//threadLocal 變數通常都是設計成 static 的,而這就是造成它會間接 reference 到 
//ClassLoader的主因....
static ThreadLocal threadLocal = new ThreadLocal() ; 

//.....

    void doSomething() {
        try{
            threadLocal.set(myObject); //儲存物件
            //... do something....
        }finally{ //這裡很重要 !
            threadLocal.set(null) ;//記得清除 reference 
        }
    }

上面這種做法是最安全的解法了。threadLocal 裡的物件在 finally的 block 裡被清除掉。不過這種狀況不是常有... 因為 threadLocal 通常是散佈在各處,而不是在一個 method 裡面可以做 try-finally。

第二個解法就是在 web app 裡加掛一個 servlet filter,在 request 結束後,將物件清除。這個可以解決第一種解法無法處理散佈各處的問題。

第三個解法就是 web app 不要在 container 裡做 restart/redeploy,不過這受限於實際的需求。任務要求 web server 不能停下來,一定要 hot-redeploy/restart web app,那就沒辦法了。

最爛的解法就是不要用 ThreadLocal...,很爛的解法... 不過你也要小心 third-party 的元件有沒有亂搞... Spring 內部裡用了很多 threadLocal 的技巧,不過他們很小心的處理這部份的 code,所以大可以放心。

最後,Sun 已經 assign 專人重寫 ThreadLocal,將在下一版 Mustang 裡改進這個問題。

ThreadLocal 近來在許多 open source framework 裡用的蠻多的,而我自己也開始學著用了... 看到這篇討論時,真的是嚇了一身冷汗,趕緊檢查自己以前的 code,發現用了三個 ThreadLocal... 而且都犯了沒有set(null)的錯誤。雖然說我們專案的 production server 不會做 restart/redeploy web app 的動作(每次都是重開 server),可是心裡總是毛毛的,難保哪一天會在不曉得的地方出錯 (memory leak 通常再現性低、也很難找....),所以還是小心點好。


回響

可以用 Tag <I>、<B>,程式碼請用 <PRE>