引入:
上文中我們用MAT工具去分析了關於堆內存的很多細節問題,而內存除了堆內存以外還有非堆內存(包括棧和方法區),而方法區PermGen,它主要存放了兩種東西,一種是被ClassLoader加載的類,一種是字符串常量,我們這篇文章着重分析PermGen上被ClassLoader所加載的類。
實踐:
思路:
因爲OutOfMemoryError時會生成HeapDump文件
(通過命令行參數 -XX:+HeapDumpOnOutOfMemoryError) ,而這個HeapDump文件不僅會包含堆信息,而且會包含Perm上的被ClassLoader所加載的Class的信息,所以我們實踐的思想就是先構造一個場景,通過無限循環讓無窮多個ClassLoader加載類,從而讓PermGen OOM,然後藉助工具分析HeapDump。
準備:
爲此,我們加入以下命令行選項:
我這裏分別解釋下:
-XX:PermSize=16M 和-XX:MaxPermSize=32M的目的是讓Perm儘可能設置小,從而縮短我們做實驗的時間。
-XX:+HeapDumpOnOutOfMemoryError的目的是爲了在OOM時候生成一個heapDump文件,而這個文件我們是可以通過工具來分析Perm上的被加載的類的信息的。
-XX:-ClassUnloading的目的是讓HotSpot VM在做Full GC時候不會對PermGen上的加載的類進行GC,從而縮短做實驗時間 (也就是讓被加載的類佔用的內存只增不減,從而更容易OOM)
-verbose 不用多說了,讓我們能看到執行,從而知道ClassLoader正在加載類
代碼:
按照上面思路,我們寫了一個程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/** *
讓無限多個Classloader去加載某個類,因爲不同的ClassLoader加載的類是不同的,所以加載後的Class會放在 *
內存的Perm區,從而最終會導致內存溢出,我們可以吧內存Perm區設置小點,然後加載一個比較大的類(比如多於1000行) *
來縮短我們的實驗時間 *
@throws Exception */ public static void makePermOutOfMemory1() throws Exception{ //用URLClassloader可以加載任意目錄/JAR包下的類的字節碼文件 String
path= "D:/Charles/Workspace2/MemoryLeakResearch/classloaded/commons-codec-1.4.jar" ; URL
url= new File(path).toURL(); URL[]
urls = {url}; //無限創建新的URLClassLoader,並且讓其加載指定的一個大類 while ( true ){ URLClassLoader
cl = new URLClassLoader(urls); cl.loadClass( "org.apache.commons.codec.binary.Base64" ); } } |
程序很簡單,就是在無限循環中創建無數多個URLClassLoader,然後讓他們去加載commons-codec-1.4.jar包中的Base64類,這個類有1000多行,算是個比較大的類,所以導致OOM發生不會花費太多時間。
這裏我們用URLClassLoader的目的是它可以加載任意目錄位置或者JAR包中的類,我們只是爲了展示這個代碼的通用性,而不要讓類侷限於本項目。
結果:
運行了十幾秒鐘後,果然OOM 發生了:
和我們設想的一樣,PermGen 溢出,並且生成了一個heap dump文件。
分析結果:
我們依然用MAT工具來分析這個heap dump文件,在"Java Basics"->"Class Loader Explorer"下,我們看到了PermGen中被各個類加載器中加載的類的情況:
從這裏可以看出,這裏有 2048-3=2045個java.net.URLClassLoader 被創建,這就是我們在無限循環中無限創建的URLClassLoader,每個ClassLoader載入了5個類,而被加載的類都放在了PermGen上,所以導致了PermGen的OutOfMemory.
我們驗證下,隨便打開某個java.net.URLClassLoader:
可以發現的確每個ClassLoader加載了5個類,分別是BinaryDecoder,BinaryEncoder,Decoder,Encoder,Base64 .
結合我們自己寫的代碼,我們是顯式的讓新建的URLClassLoader去loadClass這個Base64類的:
1
2
|
URLClassLoader
cl = new URLClassLoader(urls); cl.loadClass( "org.apache.commons.codec.binary.Base64" ); |
而Base64類會實現接口BinaryDecoder和BinaryEncoder:
所以同一個URLClassLoader也會去加載BinaryDecoder和BinaryEncoder。
而BinaryDecoder接口是繼承自Decoder接口的,BinaryEncoder接口是繼承自Encoder接口的:
所以同一個URLClassLoader也會去加載Decoder和Encoder.
總結:
(1)PermGen是JAVA規範中的”方法區”,它主要放兩部分內容,一塊是被Classloader加載的類,另一塊是字符串常量區。
(2)PermGen的ClassLoader加載的類信息,會放入heap dump文件中,而字符串常量的信息,不會放入heap dump文件中。
(3)默認HotSpot VM在做Full GC時候,回收的是Heap上的內存+PermGen上的加載的類+PermGen上的字符串常量,但是如果配置了vm參數 -XX:-ClassUnloading後 ,則只回收Heap上的內存+PermGen上的字符串常量
(3)對於Classloader加載類順序,符合“雙親委派”的加載鏈模式。