JVM Specification只是抽象的說明了JVM實例按照子系統、內存區、數據類型以及指令這幾個術語來描述的,可是規範並不是是要強制規定Java虛擬機實現內部的體系結構,更多的是爲了嚴格地定義這些實現的外部特徵。 java
Sun JVM實現中:Runtime data area (JVM 內存) 五個部分中的Java Stack, Program Counter, Native method stack三部分和規範中的描述基本一致;但對Heap 和 Method Area進行了本身獨特的實現。這個實現和Sun JVM 的Garbage collector(垃圾回收)機制有關。 算法
JVM的早期版本並無進行分區管理;這樣的後果是JVM進行垃圾回收時,不得不掃描JVM所管理的整片內存,因此蒐集垃圾是很耗費資源的事情,也是早期JAVA程序的性能低下的主要緣由。隨着JVM的發展,JVM引進了分區管理的機制。 緩存
採用分區管理機制的JVM將JVM所管理的全部內存資源分爲2個大的部分。永久存儲區(Permanent Space)和堆空間(The Heap Space)。其中堆空間又分爲新生區(Young (New) generation space)和年老區(Tenure (Old) generation space),新生區又分爲伊甸園(Eden space),倖存者0區(Survivor 0 space)和倖存者1區(Survivor 1 space)。 服務器
2.2.1 內存分區管理 併發
垃圾分代回收算法(Generational Collecting)是基於對對象生命週期分析後得出的垃圾回收算法。把對象分爲年青代、年老代、持久代,對不一樣生命週期的對象使用不一樣的算法進行回收。如今的垃圾回收器(從J2SE1.2開始)都是使用此算法的。 app
如上圖所示,爲Java堆中的各代分佈。 工具
1. 永久存儲區(Permanent Space) 性能
也稱爲持久代,圖中的Perm區,JVM specification中的 Method area 測試
是JVM的駐留內存,用於存放JDK自身所攜帶的Class, Interface的元數據,應用服務器運行必須的Class, Interface的元數據和Java程序運行時須要的Class和Interface的元數據。被裝載進此區域的數據是不會被垃圾回收器回收掉的,關閉JVM時,釋放此區域所控制的內存。 優化
這部分的空間通常不會溢出,除非一次性加載了不少的類。
在涉及到熱部署的應用服務器的時候,有時候會遇到java.lang.OutOfMemoryError : PermGen space 的錯誤,形成這個錯誤的很大緣由就有多是每次都從新部署,可是從新部署後,類的class沒有被卸載掉,這樣就形成了大量的class對象保存在了perm中,這種狀況下,通常從新啓動應用服務器能夠解決問題。 還有些應用可能動態生成或者調用一些class,例如Hibernate等,在這種時候須要設置一個比較大的持久代空間來存放這些運行過程當中新增的類,能夠經過-XX:MaxPermSize= 進行設置。
2. 堆空間(The Heap Space):
包括年輕代(young)和年老代(Tenured/old),JVM specification中的Heap
是JAVA對象生死存亡的地區,JAVA對象的出生,成長,死亡都在這個區域完成。堆空間又分別按JAVA對象的建立和年齡特徵分爲養老區和新生區。
1. Young(年輕代)
Young區的做用包括JAVA對象的建立和從JAVA對象中篩選出能進入養老區的JAVA對象。
Young區被劃分爲三部分,Eden區和兩個大小嚴格相同的Survivor區。
伊甸園(Eden space):當你的JAVA程序運行時,須要建立新的對象,JVM將在該區爲你建立一個指定的對象供程序使用。建立對象的依據便是永久存儲區中的元數據。
倖存者0區(Survivor 0 space)和倖存者1區(Survivor1 space):
Survivor區間中,某一時刻只有其中一個是被使用的,另一個留作垃圾收集時複製對象用,在Eden區間變滿的時候,minor GC就會將存活的對象移到空閒的Survivor區間中,根據JVM的策略,在通過幾回垃圾收集後,任然存活於Survivor的對象將被移動到Tenured區間。具體過程以下:
當Eden區的資源用完時,程序又須要建立對象;此時JVM的垃圾回收器將對Eden區進行垃圾回收,將Eden區中的再也不被其餘對象所引用的對象進行銷燬工做。同時將Eden區中的還有其餘對象引用的對象移動到Survivor 0區。Survivor 0區就是用於存放Eden區垃圾回收時所幸存下來的JAVA對象。當將Eden區中的還有其餘對象引用的對象移動到Survivor 0區時,若是Survivor 0區也沒有空間來存放這些對象時,JVM的垃圾回收器將對Survivor 0區進行垃圾回收處理,將Survivor 0區中不在有其餘對象引用的JAVA對象進行銷燬,將Survivor 0區中還有其餘對象引用的對象移動到Survivor 1區。此時Survivor 1區的做用就是用於存放Survivor 0區垃圾回收處理所幸存下來的JAVA對象。
須要注意,Survivor的兩個區是對稱的,沒前後關係,因此同一個區中可能同時存在從Eden複製過來對象,和從前一個Survivor複製過來的對象。並且,Survivor區總有一個是空的。
2. Tenured(年老代)
Tenured區主要保存生命週期長的對象,通常是一些老的對象,當一些對象在Young複製轉移必定的次數之後,對象就會被轉移到Tenured區,通常若是系統中用了application級別的緩存,緩存中的對象每每會被轉移到這一區間。
2.2.2 垃圾回收的過程
上面咱們看了JVM的內存分區管理,如今咱們來看JVM的垃圾回收工做是怎樣運做的。首先當啓動J2EE應用服務器時,JVM隨之啓動,並將JDK的類和接口,應用服務器運行時須要的類和接口以及J2EE應用的類和接口定義文件以及編譯後的Class文件或JAR包中的Class文件裝載到JVM的永久存儲區。在伊甸園中建立JVM,應用服務器運行時必須的JAVA對象,建立J2EE應用啓動時必須建立的JAVA對象;J2EE應用啓動完畢,可對外提供服務。
JVM在伊甸園區根據用戶的每次請求建立相應的JAVA對象,當伊甸園的空間不足以用來建立新JAVA對象的時候,JVM的垃圾回收器執行對伊甸園區的垃圾回收工做,銷燬那些再也不被其餘對象引用的JAVA對象(若是該對象僅僅被一個沒有其餘對象引用的對象引用的話,此對象也被歸爲沒有存在的必要,依此類推),並將那些被其餘對象所引用的JAVA對象移動到倖存者0區。
若是倖存者0區有足夠控件存放則直接放到倖存者0區;若是倖存者0區沒有足夠空間存放,則JVM的垃圾回收器執行對倖存者0區的垃圾回收工做,銷燬那些再也不被其餘對象引用的JAVA對象,並將那些被其餘對象所引用的JAVA對象移動到倖存者1區。
若是倖存者1區有足夠控件存放則直接放到倖存者1區;若是倖存者1區沒有足夠空間存放,則JVM的垃圾回收器執行對倖存者1區的垃圾回收工做,銷燬那些再也不被其餘對象引用的JAVA對象,並將那些被其餘對象所引用的JAVA對象移動到養老區。
若是養老區有足夠控件存放則直接放到養老區;若是養老區沒有足夠空間存放,則JVM的垃圾回收器執行對養老區的垃圾回收工做,銷燬那些再也不被其餘對象引用的JAVA對象,並保留那些被其餘對象所引用的JAVA對象。若是到最後養老區,倖存者1區,倖存者0區和伊甸園區都沒有空間的話,則JVM會報告「JVM堆空間溢出(java.lang.OutOfMemoryError: Java heap space)」,也便是在堆空間沒有空間來建立對象。
這就是JVM的內存分區管理,相比不分區來講;通常狀況下,垃圾回收的速度要快不少;由於在沒有必要的時候不用掃描整片內存而節省了大量時間。
2.2.3 內存參數設置
正如上面描述,JVM中內存被分爲了3個大的區間,JVM也提供了相應的參數來對內存大小進行配置。
下面是經常使用的JVM相關參數,一般會用這些參數進行JVM調優:
-server 啓用可以執行優化的編譯器, 顯著提升服務器的性能,但使用可以執行優化的編譯器時,服務器的預備時間將會較長。生產環境的服務器強烈推薦設置此參數。
-Xss 單個線程堆棧大小值;JDK5.0之後每一個線程堆棧大小爲1M,之前每一個線程堆棧大小爲256K。在相同物理內存下,減少這個值能生成更多的線程。可是操做系統對一個進程內的線程數仍是有限制的,不能無限生成,經驗值在3000~5000左右
-XX: +UseParNewGC 可用來設置年輕代爲併發收集【多CPU】,若是你的服務器有多個CPU,你能夠開啓此參數;開啓此參數,多個CPU可併發進行垃圾回收,可提升垃圾回收的速度。此參數和+UseParallelGC,-XX:ParallelGCThreads搭配使用。
+UseParallelGC 選擇垃圾收集器爲並行收集器。此配置僅對年輕代有效。即上述配置下,年輕代使用併發收集,而年老代仍舊使用串行收集 。可提升系統的吞吐量。
-XX:ParallelGCThreads 年輕代並行垃圾收集的前提下(對併發也有效果)的線程數,增長並行度,即:同時多少個線程一塊兒進行垃圾回收。此值最好配置與處理器數目相等。
永久存儲區相關參數(Perm Generation):
-Xnoclassgc 每次永久存儲區滿了後通常GC算法在作擴展分配內存前都會觸發一次FULL GC,除非設置了-Xnoclassgc.
-XX: PermSize 應用服務器啓動時,永久存儲區的初始內存大小。如: -XX: PermSize=16M
-XX: MaxPermSize 應用運行中,永久存儲區的極限值。爲了避免消耗擴大JVM永久存儲區分配的開銷,將此參數和-XX:PermSize這個兩個值設爲相等。
如:-XX: MaxPermSize=64M
Thread Stack
-XX:Xss=128K
堆空間相關參數
Total heap
-Xms 啓動應用時,JVM堆空間的初始大小值。
-Xmx 應用運行中,JVM堆空間的最大值,在JVM啓動之後,會分配-Xmx參數指定大小的內存給JVM,可是不必定所有使用,JVM會根據-Xms參數來調節真正用於JVM的內存。
爲了避免消耗擴大JVM堆控件分配的開銷,將此參數和-Xms這個兩個值設爲相等,考慮到須要開線程,將此值設置爲總內存的80%.
-Xmx -Xms之差就是三個Virtual空間的大小
Young Generation
-Xmn 此參數硬性規定堆空間的young區的大小,推薦設爲堆空間大小的1/4。
-XX: NewRatio=8意味着tenured 和 young的比值8:1,這樣eden+2*survivor=1/9堆內存
-XX: SurvivorRatio=32意味着eden和一個survivor的比值是32:1,這樣一個Survivor就佔Young區的1/34.
上面所列的JVM參數關係到系統的性能,而其中-XX:PermSize,-XX:MaxPermSize,-Xms,-Xmx和-Xmn這5個參數更是直接關係到系統的性能,系統是否會出現內存溢出。
-XX: PermSize和-XX: MaxPermSize分別設置應用服務器啓動時,永久存儲區的初始大小和極限大小;在生成環境中強烈推薦將這個兩個值設置爲相同的值,以免分配永久存儲區的開銷,具體的值可取系統「疲勞測試」獲取到的永久存儲區的極限值;若是不進行設置-XX: MaxPermSize默認值爲64M, 通常來講系統的類定義文件大小都會超過這個默認值。
-Xms和-Xmx分別是服務器啓動時,堆空間的初始大小和極限值。-Xms的默認值是物理內存的1/64但小於1G,-Xmx的默認值是物理內存的1/4但小於1G。在生產環境中這些默認值是確定不能知足咱們的須要的。也就是你的服務器有8g的內存,不對JVM參數進行設置優化,應用服務器啓動時仍是按默認值來分配和約束JVM對內存資源的使用,不會充分的利用全部的內存資源。
內存監控工具