天天看點

JVM 入門三闆斧

一個JVM執行個體隻存在一個堆記憶體,堆記憶體的大小是可以調節的。類加載器讀取了類檔案後,需要把類、方法、常變量放到堆記憶體中,儲存所有引用類型的真實資訊,以友善執行器執行,堆記憶體分為三部分:

Young Generation Space新生區Young

Tenure generation space養老區Old

Permanent Space永久區Perm

堆記憶體示意圖:

    1. 新生區

新生區是類的誕生、成長、消亡的區域,一個類在這裡産生,應用,最後被垃圾回收器收集,結束生命。新生區又分為兩部分: 伊甸區(Eden space)和幸存者區(Survivor pace) ,所有的類都是在伊甸區被new出來的。幸存區有兩個: 0區(Survivor 0 space)和1區(Survivor 1 space)。當伊甸園的空間用完時,程式又需要建立對象,JVM的垃圾回收器将對伊甸園區進行垃圾回收(Minor GC),将伊甸園區中的不再被其他對象所引用的對象進行銷毀。然後将伊甸園中的剩餘對象移動到幸存 0區。若幸存 0區也滿了,再對該區進行垃圾回收,然後移動到 1 區。那如果1 區也滿了呢?再移動到養老區。若養老區也滿了,那麼這個時候将産生Major GC(FullGC),進行養老區的記憶體清理。若養老區執行了Full GC之後發現依然無法進行對象的儲存,就會産生OOM異常“OutOfMemoryError”。

如果出現java.lang.OutOfMemoryError: Java heap space異常,說明Java虛拟機的堆記憶體不夠。原因有二:

(1)Java虛拟機的堆記憶體設定不夠,可以通過參數-Xms、-Xmx來調整。

(2)代碼中建立了大量大對象,并且長時間不能被垃圾收集器收集(存在被引用)。

   2. 養老區

養老區用于儲存從新生區篩選出來的 JAVA 對象,一般池對象都在這個區域活躍。

    3. 永久區

永久存儲區是一個常駐記憶體區域,用于存放JDK自身所攜帶的 Class,Interface 的中繼資料,也就是說它存儲的是運作環境必須的類資訊,被裝載進此區域的資料是不會被垃圾回收器回收掉的,關閉 JVM 才會釋放此區域所占用的記憶體。

如果出現java.lang.OutOfMemoryError: PermGen space,說明是Java虛拟機對永久代Perm記憶體設定不夠。一般出現這種情況,都是程式啟動需要加載大量的第三方jar包。例如:在一個Tomcat下部署了太多的應用。或者大量動态反射生成的類不斷被加載,最終導緻Perm區被占滿。 

Jdk1.6及之前: 有永久代, 常量池1.6在方法區

Jdk1.7:       有永久代,但已經逐漸“去永久代”,常量池1.7在堆

Jdk1.8及之後: 無永久代,常量池1.8在元空間

方法區

方法區也叫永久代。在過去(自定義類加載器還不是很常見的時候),類大多是”static”的,很少被解除安裝或收集,是以被稱為“永久的(Permanent)”。同時,由于類class是JVM實作的一部分,并不是由應用建立的,是以又被認為是“非堆(non-heap)”記憶體。

永久代也是各個線程共享的區域,它用于存儲已經被虛拟機加載過的類資訊,常量,靜态變量(JDK7中被移到Java堆),及時編譯期編譯後的代碼(類方法)等資料。這裡要講一下運作時常量池,它是方法區的一部分,用于存放編譯期生成的各種字面量和符号引用(其實就是八大基本類型的包裝類型和String類型資料(JDK7中被移到Java堆))。

在JDK1.7中的HotASpot中,已經把原本放在方法區的字元串常量池移出,

将interned String移到Java堆中

将符号Symbols移到native memory(不受GC管理的記憶體)

從JDK7開始永久代的移除工作,貯存在永久代的一部分資料已經轉移到了Java Heap或者是Native Heap。但永久代仍然存在于JDK7,并沒有完全的移除:符号引用(Symbols)轉移到了native heap;字面量(interned strings)轉移到了java heap;類的靜态變量(class statics)轉移到了java heap。

永久代在JDK1.8中進行了巨大改變,JDK1.8中移出了永久代,取而代之的是 Metaspace。

在JDK8之前的HotSpot JVM,存放這些”永久的”的區域叫做“永久代(permanent generation)”。永久代是一片連續的堆空間,在JVM啟動之前通過在指令行設定參數-XX:MaxPermSize來設定永久代最大可配置設定的記憶體空間,預設大小是64M(64位JVM由于指針膨脹,預設是85M)。永久代的垃圾收集是和老年代(old generation)捆綁在一起的,是以無論誰滿了,都會觸發永久代和老年代的垃圾收集。不過,一個明顯的問題是,當JVM加載的類資訊容量超過了參數-XX:MaxPermSize設定的值時,應用将會報OOM的錯誤(32位的JVM預設MaxPermSize是64M,而JDK8裡的Metaspace,也可以通過參數-XX:MetaspaceSize 和-XX:MaxMetaspaceSize設定大小,但如果不指定MaxMetaspaceSize的話,Metaspace的大小僅受限于native memory的剩餘大小。也就是說永久代的最大空間一定得有個指定值,而如果MaxPermSize指定不當,就會OOM)。

随着JDK8的到來,JVM不再有PermGen。但類的中繼資料資訊(metadata)還在,隻不過不再是存儲在連續的堆空間上,而是移動到叫做“Metaspace”的本地記憶體(Native memory)中。

由于類的中繼資料可以在本地記憶體(native memory)之外配置設定,是以其最大可利用空間是整個系統記憶體的可用空間。這樣,你将不再會遇到OOM錯誤,溢出的記憶體會湧入到交換空間(swap)。最終使用者可以為類中繼資料指定最大可利用的本地記憶體空間,JVM也可以增加本地記憶體空間來滿足類中繼資料資訊的存儲。

永久存儲區是一個常駐記憶體區域,用于存放JDK自身所攜帶的 Class,Interface 的中繼資料,也就是說它存儲的是運作環境必須的類資訊,被裝載進此區域的資料是不會被垃圾回收器回收掉的,關閉 JVM 才會釋放此區域所占用的記憶體。

如果出現java.lang.OutOfMemoryError: PermGen space,說明是Java虛拟機對永久代Perm記憶體設定不夠。一般出現這種情況,都是程式啟動需要加載大量的第三方jar包。例如:在一個Tomcat下部署了太多的應用。或者大量動态反射生成的類不斷被加載,最終導緻Perm區被占滿。 

Jdk1.6及之前: 有永久代, 常量池1.6在方法區

Jdk1.7:       有永久代,但已經逐漸“去永久代”,常量池1.7在堆

Jdk1.8及之後: 無永久代,常量池1.8在元空間

繼續閱讀