Tomcat與內存泄露處理
似乎從 Java 入門的時候,就有這樣的說法來考查 Java開發者:
Java 不像 C++ 那樣自己管理內存,有Java 虛擬機負責進行垃圾回收,再也沒有內存泄露的問題了。 |
但是隨著開發經歷的增長,已經開發過應用的增多,應用內需要加載的 class 增多,經常就會遇到內存溢出(OOM)。或者更確切的說,因為加載 class 的增多導致的內存溢出是
- java.lang.OutOfMemoryError: PermGen space
此時,解決OOM的方式一般是:
1. 分析應用的代碼寫的是否有問題,可以通過一些工具觀察應用內占用內存較多的 class 類型 (比如通過 JVisualVM 來分析Java七武器系列多情環 --多功能Profiling工具 JVisual VM,或者通過MAT來分析)
2. 修改 JVM啟動參數,增大關于 Perm Gen 的配置。
在 Tomcat 這一類的 應用服務器中,由于其做為應用的容器運行,可能自身的Perm Gen 占用并不多,但需要考慮部署到容器中的應用占用。有些應用依賴了大量的第三方類庫,也有一些應用會在運行時動態生成大量的 class,這些內容的加載,都容易導致 Perm Gen 的 OOM。
對于 OOM 的處理,內部會在啟動時占用一小塊內存,在 OOM 產生的時候釋放掉來臨時緩解一下,這種稱為oomParachute。
除此之外,Tomcat 在 manager 應用中還提供了發現內存泄漏的功能。
圖上說明寫的明白,該功能主要用于分析在應用停止、重部署、解除部署時是否造成了內存泄漏。
在請求后,manager的上方信息顯示區域會提示當前是否有應用造成的內存泄漏。
但需要注意的是此功能會觸發一次 Full GC 的執行,代碼中使用的是 System.gc(),在生產環境中如果使用需要謹慎。
那么,在什么情況下會導致所謂的應用內存泄漏呢?
我們都知道, 為了實現應用間的 class 隔離, Tomcat 對于每個應用,都會單獨使用一個 WebappClassLoader,這樣,多個應用間即使都使用到一個 類庫的不同版本,也不會相互影響造成沖突。
但是,在這種情況下,當一個應用已經執行了停止操作,或者執行了重部署操作,此時是會生成一個新的 classLoader 來加載新部署的應用類信息。
我們知道,在 Java 中,類與類之間是存在引用關系的,類似于強引用,弱引用,幻影引用,用來在GC時將一些不需要的 class 回收掉,騰出空間。按理說之前的 classLoader 本應該被垃圾回收,但在某些時候,由于一些類之前的引用關系導致該 classLoader,以及其加載的一系列 class 文件, 都不能被標識為垃圾,此時這些 class 依然駐留在 Perm Gen,隨著應用多次啟停,多次重部署之后,出現了 Perm Gen 的 OOM。
一般以下類庫的使用容易導致 class loader 逃過垃圾回收,產生內存泄漏:
- JDBC driver 注冊
- 一些 logging 框架
- 沒有移除的 ThreadLocal的使用
- 未停止的 Thread
此外,一些 Java API 的使用也容易導致此問題,例如
- javax.imageio API
- XML 解析
- RMI 使用
由于這些容易占用 classLoader,導致其不能被回收,如果這些 class 交給各個應用的類加載器進行加載,就會使得 Perm Gen 中這些類越來越多,從而產生泄漏。
為此,在 Tomcat 中引入了JreMemoryLeakPreventionListener 這個組件。實現思路是在 Tomcat 啟動時,通過 System class Loader 來加載這些類。 由于類加載器的加載原理(默認父優先,而且這些系統的類,都會委托給系統類加載器進行加載),這些類不會再被 WebclassLoader 重新加載,從而減小內存泄漏的產生。
默認在 Tomcat 的配置 server.xml 中已經開啟了該組件,所以這些功能你已經不知不覺中在使用。
【本文為51CTO專欄作者“侯樹成”的原創稿件,轉載請通過作者微信公眾號『Tomcat那些事兒』獲取授權】