http://www.cnblogs.com/paddix/p/5309550.htmlhtml
1、JVM 內存模型java
根據 JVM 規範,JVM 內存共分爲虛擬機棧、堆、方法區、程序計數器、本地方法棧五個部分。程序員
一、虛擬機棧:每一個線程有一個私有的棧,隨着線程的建立而建立。棧裏面存着的是一種叫「棧幀」的東西,每一個方法會建立一個棧幀,棧幀中存放了局部變量表(基本數據類型和對象引用)、操做數棧、方法出口等信息。棧的大小能夠固定也能夠動態擴展。當棧調用深度大於JVM所容許的範圍,會拋出StackOverflowError的錯誤,不過這個深度範圍不是一個恆定的值,咱們經過下面這段程序能夠測試一下這個結果:數組
棧溢出測試源碼:jsp
1性能 2測試 3url 4spa 5.net 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
代碼段 1
運行三次,能夠看出每次棧的深度都是不同的,輸出結果以下。
至於紅色框裏的值是怎麼出來的,就須要深刻到 JVM 的源碼中才能探討,這裏不做詳細闡述。
虛擬機棧除了上述錯誤外,還有另外一種錯誤,那就是當申請不到空間時,會拋出 OutOfMemoryError。這裏有一個小細節須要注意,catch 捕獲的是 Throwable,而不是 Exception。由於 StackOverflowError 和 OutOfMemoryError 都不屬於 Exception 的子類。
二、本地方法棧:
這部分主要與虛擬機用到的 Native 方法相關,通常狀況下, Java 應用程序員並不須要關心這部分的內容。
三、PC 寄存器:
PC 寄存器,也叫程序計數器。JVM支持多個線程同時運行,每一個線程都有本身的程序計數器。假若當前執行的是 JVM 的方法,則該寄存器中保存當前執行指令的地址;假若執行的是native 方法,則PC寄存器中爲空。
四、堆
堆內存是 JVM 全部線程共享的部分,在虛擬機啓動的時候就已經建立。全部的對象和數組都在堆上進行分配。這部分空間可經過 GC 進行回收。當申請不到空間時會拋出 OutOfMemoryError。下面咱們簡單的模擬一個堆內存溢出的狀況:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
代碼段 2
運行上述代碼,輸出結果以下:
注意,這裏我指定了堆內存的大小爲16M,因此這個地方顯示的count=14(這個數字不是固定的),至於爲何會是14或其餘數字,須要根據 GC 日誌來判斷,具體緣由會在下篇文章中給你們解釋。
五、方法區:
方法區也是全部線程共享。主要用於存儲類的信息、常量池、方法數據、方法代碼等。方法區邏輯上屬於堆的一部分,可是爲了與堆進行區分,一般又叫「非堆」。 關於方法區內存溢出的問題會在下文中詳細探討。
2、PermGen(永久代)
絕大部分 Java 程序員應該都見過 "java.lang.OutOfMemoryError: PermGen space "這個異常。這裏的 「PermGen space」其實指的就是方法區。不過方法區和「PermGen space」又有着本質的區別。前者是 JVM 的規範,然後者則是 JVM 規範的一種實現,而且只有 HotSpot 纔有 「PermGen space」,而對於其餘類型的虛擬機,如 JRockit(Oracle)、J9(IBM) 並無「PermGen space」。因爲方法區主要存儲類的相關信息,因此對於動態生成類的狀況比較容易出現永久代的內存溢出。最典型的場景就是,在 jsp 頁面比較多的狀況,容易出現永久代內存溢出。咱們如今經過動態生成類來模擬 「PermGen space」的內存溢出:
1 2 3 4 |
|
代碼段 3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
代碼段 4
運行結果以下:
本例中使用的 JDK 版本是 1.7,指定的 PermGen 區的大小爲 8M。經過每次生成不一樣URLClassLoader對象來加載Test類,從而生成不一樣的類對象,這樣就能看到咱們熟悉的 "java.lang.OutOfMemoryError: PermGen space " 異常了。這裏之因此採用 JDK 1.7,是由於在 JDK 1.8 中, HotSpot 已經沒有 「PermGen space」這個區間了,取而代之是一個叫作 Metaspace(元空間) 的東西。下面咱們就來看看 Metaspace 與 PermGen space 的區別。
3、Metaspace(元空間)
其實,移除永久代的工做從JDK1.7就開始了。JDK1.7中,存儲在永久代的部分數據就已經轉移到了Java Heap或者是 Native Heap。但永久代仍存在於JDK1.7中,並沒徹底移除,譬如符號引用(Symbols)轉移到了native heap;字面量(interned strings)轉移到了java heap;類的靜態變量(class statics)轉移到了java heap。咱們能夠經過一段程序來比較 JDK 1.6 與 JDK 1.7及 JDK 1.8 的區別,以字符串常量爲例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
這段程序以2的指數級不斷的生成新的字符串,這樣能夠比較快速的消耗內存。咱們經過 JDK 1.六、JDK 1.7 和 JDK 1.8 分別運行:
JDK 1.6 的運行結果:
JDK 1.7的運行結果:
JDK 1.8的運行結果:
從上述結果能夠看出,JDK 1.6下,會出現「PermGen Space」的內存溢出,而在 JDK 1.7和 JDK 1.8 中,會出現堆內存溢出,而且 JDK 1.8中 PermSize 和 MaxPermGen 已經無效。所以,能夠大體驗證 JDK 1.7 和 1.8 將字符串常量由永久代轉移到堆中,而且 JDK 1.8 中已經不存在永久代的結論。如今咱們看看元空間究竟是一個什麼東西?
元空間的本質和永久代相似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。所以,默認狀況下,元空間的大小僅受本地內存限制,但能夠經過如下參數來指定元空間的大小:
-XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對該值進行調整:若是釋放了大量的空間,就適當下降該值;若是釋放了不多的空間,那麼在不超過MaxMetaspaceSize時,適當提升該值。
-XX:MaxMetaspaceSize,最大空間,默認是沒有限制的。
除了上面兩個指定大小的選項之外,還有兩個與 GC 相關的屬性:
-XX:MinMetaspaceFreeRatio,在GC以後,最小的Metaspace剩餘空間容量的百分比,減小爲分配空間所致使的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC以後,最大的Metaspace剩餘空間容量的百分比,減小爲釋放空間所致使的垃圾收集
如今咱們在 JDK 8下從新運行一下代碼段 4,不過此次再也不指定 PermSize 和 MaxPermSize。而是指定 MetaSpaceSize 和 MaxMetaSpaceSize的大小。輸出結果以下:
從輸出結果,咱們能夠看出,此次再也不出現永久代溢出,而是出現了元空間的溢出。
4、總結
經過上面分析,你們應該大體瞭解了 JVM 的內存劃分,也清楚了 JDK 8 中永久代向元空間的轉換。不過你們應該都有一個疑問,就是爲何要作這個轉換?因此,最後給你們總結如下幾點緣由:
一、字符串存在永久代中,容易出現性能問題和內存溢出。
二、類及方法的信息等比較難肯定其大小,所以對於永久代的大小指定比較困難,過小容易出現永久代溢出,太大則容易致使老年代溢出。
三、永久代會爲 GC 帶來沒必要要的複雜度,而且回收效率偏低。
四、Oracle 可能會將HotSpot 與 JRockit 合二爲一。