不少開發者都在其系統中見過「java.lang.OutOfMemoryError: PermGen space」這一問題。這每每是由類加載器相關的內存泄漏以及新類加載器的建立致使的,一般出現於代碼熱部署時。相對於正式產品,該問題在開發機上出現的頻率更高,在產品中最多見的「問題」是默認值過低了。經常使用的解決方法是將其設置爲256MB或更高。java
PermGen space的全稱是Permanent Generation space,是指內存的永久保存區域,說說爲何會內存益出:這一部分用於存放Class和Meta的信息,Class在被 Load的時候被放入PermGen space區域,它和和存放Instance的Heap區域不一樣,因此若是你的APP會LOAD不少CLASS的話,就極可能出現PermGen space錯誤。這種錯誤常見在web服務器對JSP進行pre compile的時候。web
JVM 種類有不少,好比 Oralce-Sun Hotspot, Oralce JRockit, IBM J9, Taobao JVM(淘寶好樣的!)等等。固然武林盟主是Hotspot了,這個毫無爭議。須要注意的是,PermGen space是Oracle-Sun Hotspot纔有,JRockit以及J9是沒有這個區域。緩存
JDK8 HotSpot JVM 將移除永久區,使用本地內存來存儲類元數據信息並稱之爲:元空間(Metaspace);這與Oracle JRockit 和IBM JVM’s很類似,以下圖所示服務器
這意味着不會再有java.lang.OutOfMemoryError: PermGen問題,也再也不須要你進行調優及監控內存空間的使用……但請等等,這麼說還爲時過早。在默認狀況下,這些改變是透明的,接下來咱們的展現將使你知道仍然要關注類元數據內存的佔用。請必定要牢記,這個新特性也不能神奇地消除類和類加載器致使的內存泄漏。工具
java8中metaspace總結以下:性能
這部份內存空間將所有移除。測試
JVM的參數:PermSize 和 MaxPermSize 會被忽略並給出警告(若是在啓用時設置了這兩個參數)。url
大部分類元數據都在本地內存中分配。spa
用於描述類元數據的「klasses」已經被移除。操作系統
默認狀況下,類元數據只受可用的本地內存限制(容量取決因而32位或是64位操做系統的可用虛擬內存大小)。
新參數(MaxMetaspaceSize)用於限制本地內存分配給類元數據的大小。若是沒有指定這個參數,元空間會在運行時根據須要動態調整。
對於僵死的類及類加載器的垃圾回收將在元數據使用達到「MaxMetaspaceSize」參數的設定值時進行。
適時地監控和調整元空間對於減少垃圾回收頻率和減小延時是頗有必要的。持續的元空間垃圾回收說明,可能存在類、類加載器致使的內存泄漏或是大小設置不合適。
一些雜項數據已經移到Java堆空間中。升級到JDK8以後,會發現Java堆 空間有所增加。
元空間的使用狀況能夠從HotSpot1.8的詳細GC日誌輸出中獲得。
Jstat 和 JVisualVM兩個工具,在使用b75版本進行測試時,已經更新了,可是仍是能看到老的PermGen空間的出現。
前面已經從理論上充分說明,下面讓咱們經過「泄漏」程序進行新內存空間的觀察……
爲了更好地理解Metaspace內存空間的運行時行爲,
將進行如下幾種場景的測試:
使用JDK1.7運行Java程序,監控並耗盡默認設定的85MB大小的PermGen內存空間。
使用JDK1.8運行Java程序,監控新Metaspace內存空間的動態增加和垃圾回收過程。
使用JDK1.8運行Java程序,模擬耗盡經過「MaxMetaspaceSize」參數設定的128MB大小的Metaspace內存空間。
首先創建了一個模擬PermGen OOM的代碼
public class ClassA { public void method(String name) { // do nothing } }
上面是一個簡單的ClassA,把他編譯成class字節碼放到D:/classes下面,測試代碼中用URLClassLoader來加載此類型上面類編譯成class
/** * 模擬PermGen OOM * @author benhail */ public class OOMTest { public static void main(String[] args) { try { //準備url URL url = new File("D:/classes").toURI().toURL(); URL[] urls = {url}; //獲取有關類型加載的JMX接口 ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean(); //用於緩存類加載器 List<ClassLoader> classLoaders = new ArrayList<ClassLoader>(); while (true) { //加載類型並緩存類加載器實例 ClassLoader classLoader = new URLClassLoader(urls); classLoaders.add(classLoader); classLoader.loadClass("ClassA"); //顯示數量信息(共加載過的類型數目,當前還有效的類型數目,已經被卸載的類型數目) System.out.println("total: " + loadingBean.getTotalLoadedClassCount()); System.out.println("active: " + loadingBean.getLoadedClassCount()); System.out.println("unloaded: " + loadingBean.getUnloadedClassCount()); } } catch (Exception e) { e.printStackTrace(); } } }
虛擬機器參數設置以下:-verbose -verbose:gc
設置-verbose參數是爲了獲取類型加載和卸載的信息
設置-verbose:gc是爲了獲取垃圾收集的相關信息
Java1.7的PermGen默認空間爲85 MB(或者能夠經過-XX:MaxPermSize=XXXm指定)
能夠從上面的JVisualVM的截圖看出:當加載超過6萬個類以後,PermGen被耗盡。咱們也能經過程序和GC的輸出觀察耗盡的過程。
程序輸出(摘取了部分)
...... [Loaded ClassA from file:/D:/classes/] total: 64887 active: 64887 unloaded: 0 [GC 245041K->213978K(536768K), 0.0597188 secs] [Full GC 213978K->211425K(644992K), 0.6456638 secs] [GC 211425K->211425K(656448K), 0.0086696 secs] [Full GC 211425K->211411K(731008K), 0.6924754 secs] [GC 211411K->211411K(726528K), 0.0088992 secs] ............... java.lang.OutOfMemoryError: PermGen space
Java的Metaspace空間:不受限制 (默認)
從上面的截圖能夠看到,JVM Metaspace進行了動態擴展,本地內存的使用由20MB增加到646MB,以知足程序中不斷增加的類數據內存佔用需求。咱們也能觀察到JVM的垃圾回收事件—試圖銷燬僵死的類或類加載器對象。可是,因爲咱們程序的泄漏,JVM別無選擇只能動態擴展Metaspace內存空間。程序加載超過10萬個類,而沒有出現OOM事件。
Java的Metaspace空間:128MB(-XX:MaxMetaspaceSize=128m)
能夠從上面的JVisualVM的截圖看出:當加載超過2萬個類以後,Metaspace被耗盡;與JDK1.7運行時很是類似。咱們也能經過程序和GC的輸出觀察耗盡的過程。另外一個有趣的現象是,保留的原生內存佔用量是設定的最大大小兩倍之多。這可能代表,若是可能的話,可微調元空間容量大小策略,來避免本地內存的浪費。
從Java程序的輸出中看到以下異常。
[Loaded ClassA from file:/D:/classes/] total: 21393 active: 21393 unloaded: 0 [GC (Metadata GC Threshold) 64306K->57010K(111616K), 0.0145502 secs] [Full GC (Metadata GC Threshold) 57010K->56810K(122368K), 0.1068084 secs] java.lang.OutOfMemoryError: Metaspace
在設置了MaxMetaspaceSize的狀況下,該空間的內存仍然會耗盡,進而引起「java.lang.OutOfMemoryError: Metadata space」錯誤。由於類加載器的泄漏仍然存在,而一般Java又不但願無限制地消耗本機內存,所以設置一個相似於MaxPermSize的限制看起來也是合理的。
以前不論是不是須要,JVM都會吃掉那塊空間……若是設置得過小,JVM會死掉;若是設置得太大,這塊內存就被JVM浪費了。理論上說,如今你徹底能夠不關注這個,由於JVM會在運行時自動調校爲「合適的大小」;
提升Full GC的性能,在Full GC期間,Metadata到Metadata pointers之間不須要掃描了,別小看這幾納秒時間;
隱患就是若是程序存在內存泄露,像OOMTest那樣,不停的擴展metaspace的空間,會致使機器的內存不足,因此仍是要有必要的調試和監控。