JVM記憶體模型總結
JVM組成部分:方法區、堆;虛拟機棧、本地方法棧、程式計數器。
JVM占用的記憶體分為線程共享區 和線程私有區,線程共享區和JVM同生共死,所有線程均可通路此區域;而線程私有區顧名思義每個線程各自占有,與各自線程同生共死。
方法區: 用于存儲類資訊、常量池、靜态變量、JIT編譯後的代碼等資料,具體放在哪裡,不同的實作可以放在不同的地方。
堆: 存放所有new出來的對象,是GC垃圾回收主要場所。
虛拟機棧: 每個方法執行的同時會建立一個棧幀存儲方法的局部變量表、操作數棧、動态連接配接和方法傳回位址等資訊;每一個方法從調用開始至執行完成的過程,對應一個棧幀在虛拟機裡面從入棧到出棧。
本地方法棧: 與虛拟機棧基本類似,差別在于虛拟機棧為虛拟機執行的java方法服務,而本地方法棧則是為Native(本地)方法服務。
程式計數器: 目前線程所執行的位元組碼的行号訓示器,位元組碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、循環、異常處理、線程恢複等基礎功能都需要依賴計數器完成。
垃圾回收機制
超級棒得一篇文章,轉載至:https://blog.51cto.com/lizhenliang/2164876?wx=
JVM記憶體劃分示意圖
1、JVM記憶體劃分為堆記憶體和非堆記憶體,堆記憶體分為年輕代(Young Generation)、老年代(Old Generation),非堆記憶體就一個永久代(Permanent Generation)。
2、年輕代又分為Eden和Survivor區。Survivor區由FromSpace(S0)和ToSpace(S1)組成。Eden區占大容量,Survivor兩個區占小容量,預設比例是8:1:1。
3、堆記憶體用途:存放的是對象,垃圾收集器就是收集這些對象,然後根據GC算法回收。
4、非堆記憶體用途:永久代,也稱為方法區,存儲程式運作時長期存活的對象,比如類的中繼資料、方法、常量、屬性等。
在JDK1.8版本廢棄 了永久代,替代的是元空間 (MetaSpace),元空間與永久代上類似,都是方法區的實作,他們最大差別是:元空間并不在JVM中,而是使用本地記憶體。
元空間要注意有兩個參數:
MetaspaceSize :初始化元空間大小,控制發生GC門檻值
MaxMetaspaceSize : 限制元空間大小上限,防止異常占用過多實體記憶體
為什麼移除永久代?
移除永久代原因:為融合HotSpot JVM與JRockit VM(新JVM技術)而做出的改變,因為JRockit沒有永久代。
有了元空間就不再會出現永久代OOM(Out Of Memory)記憶體用完問題了!
分代概念
新生成的對象首先放到年輕代Eden區,當Eden空間滿了,觸發Minor GC,存活下來的對象移動到Survivor0區,Survivor0區滿後觸發執行Minor GC,Survivor0區存活對象移動到Suvivor1區,這樣保證了一段時間内總有一個survivor區為空。 經過多次Minor GC仍然存活的對象移動到老年代。
老年代存儲長期存活的對象,占滿時會觸發Major GC=Full GC,GC期間會停止所有線程等待GC完成,是以對響應要求高的應用盡量減少發生Major GC,避免響應逾時。
Minor GC : 清理年輕代
Major GC : 清理老年代
Full GC : 清理整個堆空間,包括年輕代和永久代
所有GC都會停止應用所有線程。
為什麼分代
将對象根據存活機率進行分類,對存活時間長的對象,放到固定區,進而減少掃描垃圾時間及GC頻率。針對分類進行不同的垃圾回收算法,對算法揚長避短。
為什麼survivor分為兩塊相等大小的幸存空間
主要為了解決碎片化。如果記憶體碎片化嚴重,也就是兩個對象占用不連續的記憶體,已有的連續記憶體不夠新對象存放,就會觸發GC。
JVM堆記憶體常用參數
-Xms 堆記憶體初始大小,機關m、g
-Xmx(MaxHeapSize) 堆記憶體最大允許大小,一般不要大于實體記憶體的80%
-XX:PermSize 非堆記憶體初始大小,一般應用設定初始化200m,最大1024m就夠了
-XX:MaxPermSize 非堆記憶體最大允許大小
-XX:NewSize(-Xns) 年輕代記憶體初始大小
-XX:MaxNewSize(-Xmn) 年輕代記憶體最大允許大小,也可以縮寫
-XX:SurvivorRatio=8 年輕代中Eden區與Survivor區的容量比例值,預設為8,即8:1
-Xss 堆棧記憶體大小
垃圾回收算法(GC,Garbage Collection)
紅色是标記的非活動對象,綠色是活動對象。
1、标記-清除(Mark-Sweep)
GC分為兩個階段,标記和清除。首先标記所有可回收的對象,在标記完成後統一回收所有被标記的對象。同時會産生不連續的記憶體碎片。碎片過多會導緻以後程式運作時需要配置設定較大對象時,無法找到足夠的連續記憶體,而不得已再次觸發GC。
2、複制(Copy)
将記憶體按容量劃分為兩塊,每次隻使用其中一塊。當這一塊記憶體用完了,就将存活的對象複制到另一塊上,然後再把已使用的記憶體空間一次清理掉。這樣使得每次都是對半個記憶體區回收,也不用考慮記憶體碎片問題,簡單高效。缺點需要兩倍的記憶體空間。
3、标記-整理(Mark-Compact)
也分為兩個階段,首先标記可回收的對象,再将存活的對象都向一端移動,然後清理掉邊界以外的記憶體。此方法避免标記-清除算法的碎片問題,同時也避免了複制算法的空間問題。
一般年輕代中執行GC後,會有少量的對象存活,就會選用複制算法,隻要付出少量的存活對象複制成本就可以完成收集。而老年代中因為對象存活率高,沒有額外過多記憶體空間配置設定,就需要使用标記-清理或者标記-整理算法來進行回收。
垃圾收集器
1、串行收集器(Serial)
比較老的收集器,單線程。收集時,必須暫停應用的工作線程,直到收集結束。
2、并行收集器(Parallel)
多條垃圾收集線程并行工作,在多核CPU下效率更高,應用線程仍然處于等待狀态。
3、CMS收集器(Concurrent Mark Sweep)
CMS收集器是縮短暫停應用時間為目标而設計的,是基于标記-清除算法實作,整個過程分為4個步驟,包括:
(1)初始标記(Initial Mark)
(2)并發标記(Concurrent Mark)
(3)重新标記(Remark)
(4)并發清除(Concurrent Sweep)
其中,初始标記、重新标記這兩個步驟仍然需要暫停應用線程。初始标記隻是标記一下GC Roots能直接關聯到的對象,速度很快,并發标記階段是标記可回收對象,而重新标記階段則是為了修正并發标記期間因使用者程式繼續運作導緻标記産生變動的那一部分對象的标記記錄,這個階段暫停時間比初始标記階段稍長一點,但遠比并發标記時間段。
由于整個過程中消耗最長的并發标記和并發清除過程收集器線程都可以與使用者線程一起工作,是以,CMS收集器記憶體回收與使用者一起并發執行的,大大減少了暫停時間。
4、G1收集器(Garbage First)
G1收集器将堆記憶體劃分多個大小相等的獨立區域(Region),并且能預測暫停時間,能預測原因它能避免對整個堆進行全區收集。G1跟蹤各個Region裡的垃圾堆積價值大小(所獲得空間大小以及回收所需時間),在背景維護一個優先清單,每次根據允許的收集時間,優先回收價值最大的Region,進而保證了再有限時間内獲得更高的收集效率。
G1收集器工作工程分為4個步驟,包括:
(1)初始标記(Initial Mark)
(2)并發标記(Concurrent Mark)
(3)最終标記(Final Mark)
(4)篩選回收(Live Data Counting and Evacuation)
初始标記與CMS一樣,标記一下GC Roots能直接關聯到的對象。并發标記從GC Root開始标記存活對象,這個階段耗時比較長,但也可以與應用線程并發執行。而最終标記也是為了修正在并發标記期間因使用者程式繼續運作而導緻标記産生變化的那一部分标記記錄。最後在篩選回收階段對各個Region回收價值和成本進行排序,根據使用者所期望的GC暫停時間來執行回收。
垃圾收集器參數
-XX:+UseSerialGC 串行收集器
-XX:+UseParallelGC 并行收集器
-XX:+UseParallelGCThreads=8 并行收集器線程數,同時有多少個線程進行垃圾回收,一般與CPU數量相等
-XX:+UseParallelOldGC 指定老年代為并行收集
-XX:+UseConcMarkSweepGC CMS收集器(并發收集器)
-XX:+UseCMSCompactAtFullCollection 開啟記憶體空間壓縮和整理,防止過多記憶體碎片
-XX:CMSFullGCsBeforeCompaction=0 表示多少次Full GC後開始壓縮和整理,0表示每次Full GC後立即執行壓縮和整理
-XX:CMSInitiatingOccupancyFraction=80% 表示老年代記憶體空間使用80%時開始執行CMS收集,防止過多的Full GC
-XX:+UseG1GC G1收集器
-XX:MaxTenuringThreshold=0 在年輕代經過幾次GC後還存活,就進入老年代,0表示直接進入老年代
為什麼會堆記憶體溢出
在年輕代中經過GC後還存活的對象會被複制到老年代中。當老年代空間不足時,JVM會對老年代進行完全的垃圾回收(Full GC)。如果GC後,還是無法存放從Survivor區複制過來的對象,就會出現OOM(Out of Memory)。
OOM(Out of Memory)異常常見有以下幾個原因
1)老年代記憶體不足:java.lang.OutOfMemoryError:Javaheapspace
2)永久代記憶體不足:java.lang.OutOfMemoryError:PermGenspace
3)代碼bug,占用記憶體無法及時回收。