1 線程共享內存區
Java堆區
- 用於存儲Java對象實例,可是不必定是Java對象內存分配的惟一選擇(爲了下降GC頻率).在JVM啓動的時候大小就已經設定好了.(-Xmx最大 -Xms起始) 超過最大內存的時候,拋出OOM異常.
- 實際的內存空間能夠不連續,是GC的重點區域.
- YoungGen新生代(Eden, From Survivor, To Survivor) ; OldGen老年代(OldGen).根據不一樣的代選擇不一樣的GC算法.
方法區
- 存儲了每個Java類的結構信息,好比 運行時常量池 , 字段和方法數據 , 構造函數 , 普通方法的字節碼內容 以及 類,實例,接口初始化時要用到的特殊方法等數據.
- jvm規範沒有對方法區的實現有明確要求,在HotSpot中,它屬於Java堆區的一部分
- 在JVM啓動的時候建立,也能夠不連續,有時候被稱爲永久代,它不會頻繁的GC,可是不表明它裏面數據永遠不會被回收.若是沒有顯示要求不對方法區進行內存回收的狀況下,GC的回收目標僅針對方法區的常量池和類型卸載
- 超過
-XX:Max-PermSize
也會OOM
運行時常量池
- 屬於方法區的一部分.運行時常量池就是字節碼文件中常量池表的運行時表示形式.
- 當類加載器成功的將一個類或者接口裝載進JVM後,就會建立與之對應的運行時常量池.
2 線程私有內存區
PC寄存器
- 無OOM
- 爲何每一個線程都要?答:CPU作任務切換,每一個線程都要記錄正在執行的當前字節碼指令地址,從而每一個線程均可以獨立計算.
- JVM的解釋器須要經過改變PC寄存器的值來明確下一條有關執行什麼樣的字節碼指令
Java棧(Java虛擬機棧)
- Java棧用於存儲棧幀,生命週期和線程的生命週期一致
- 對應Java堆中存儲對象,Java棧中局部變量表用於存儲各種原始數據類型,對象引用,以及returnAdress類型.
3 自動內存管理
內存分配原理
-
jvm包含三種引用了類型:類類型,數組類型,接口類型,分別對應建立的值是 類實例, 數組實例, 以及實現了某個接口的派生類實例.java
-
下圖是jvm中具體的建立對象實例 算法
-
分配內存有指針碰撞(已用的未用的分兩邊,經過修改中間指針的偏移量) 和 空閒列表(Free List)數組
堆區和方法區
- 堆區和方法區是線程共享,在併發環境下從堆區中劃份內存空間是非線程安全的.因此一個類若是在分配內存以前已經成功完成類裝載步驟以後,JVM會優先選擇在TLAB(本地線程分配緩衝區,是堆區中一塊線程私有的區域,包含在Eden空間內,缺省很小,佔Eden的1%,能夠經過參數調整),能夠避免一系列的非線程安全問題,還能提高內存分配的吞吐量.這種內存分配方式叫作快速分配策略.(-XX:UseTLAB)
- 一旦對象在TLAB空間分配內存失敗,JVM會嘗試經過加鎖來確保原子性,從而直接在Eden分配內存.
- 分配好內存空間後,接下來就是初始化對象實例,首先對分配的內存空間進行零值初始化,再接下來是初始化對象頭和實例數據.到這裏,一個Java對象實例纔是真正的建立成功.
逃逸分析與棧上分配
- 一個對象被定義在方法體內部以後,一旦其應用被外部成員引用,這個對象就發生了逃逸,反之,JVM就會爲其在棧幀中分配內存空間(這是優化技術)
public class StackAllocation {
public StackAllocation obj;
//逃逸
public StackAllocation getStackAllocation() {
return null == obj ? new StackAllocation() : obj;
}
//爲成員變量賦值,逃逸
public void setStackAllocation() {
obj = new StackAllocation();
}
//引用成員變量的值,逃逸
public void useStackAllocation1() {
StackAllocation obj = getStackAllocation();
}
//對象的做用於僅在方法體內,未逃逸
public void useStackAllocation2() {
StackAllocation obj = new StackAllocation;
}
}
對象內存佈局與OOP-Klass模型
GC算法和垃圾收集器
- GC(Garbage Collector 垃圾收集器),其實內存劃分(新生代,老年代)是徹底依賴於GC的設計.當內存空間中的內存消耗達到必定的閾值以後,GC會進行垃圾回收.
- 能夠根據如下6點評估一款GC的性能
- 吞吐量:程序的運行時間/(程序的運行時間 + 內存回收的時間)
- 垃圾收集開銷
- 暫停時間
- 收集頻率
- 堆空間:java堆區鎖佔用的內存大小
- 快速:一個對象從誕生到被垃圾回收所經歷的時間.
垃圾標記: 根搜索算法
- 通常不用引用計數法.沒法解決死亡對象的互相引用致使沒法垃圾回收,因此HotSpot使用根搜索算法,只有可以被根對象集合直接或者間接鏈接的對象纔是存活對象.當目標對象不可達的時候,即可以在instanceOopDesc的MarkWord中將其標記爲垃圾對象.
- 根對象集合:
- Java棧中的對象引用
- 本地方法棧中的對象引用
- 運行時常量池中的對象引用
- 方法區中類靜態屬性的對象引用(因此這個要注意 靜態變量的定義!)
- 與一個類對應的惟一數據類型的Class對象
垃圾回收:分代收集算法
- 上一步成功的區分出了存活和死亡對象.接下來就是垃圾回收算法.
標記-清除算法(Mark-Sweep)
- 把垃圾回收任務分爲兩個階段,分別是垃圾回收和內存釋放.簡單,可是效率低下,並且會產生內存碎片
複製算法(Copying)
-
就是由於這個算法,因此內存是分代的!安全
-
java的大多數對象都是瞬時對象,生命週期很是短.複製算法普遍用於新生代中.新生代分爲Eden,From Suvivor和To Suvivor,Eden和另外兩個Survior空間缺省所佔比例爲8:1,能夠經過-XX:SurvivorRatio
調整.併發
-
當執行一次Minor GC,Eden中存活的對象會被複制到To Suvivor空間內,而且以前經歷過一次Minor GC並在From Suvivor存活下來的對象若是還年輕的話會被複制到To Suvivor.jvm
-
知足兩種特殊狀況,Eden和From空間的存活對象不會被複制到To空間.函數
- 存活的對象的分代年齡超過
-XX:MaxTenuringThreshold
所指的的閾值時,會直接晉升到老年代中.
- 當To空間的容量達到閾值的時候,存活的對象也會直接晉升到老年代中
- 當執行完Minor GC以後,Eden空間和From空間將會被清空,存活下來的對象會所有存在To空間內,接下來From空間和To空間將會互換位置(無非就是使用To空間做爲一個臨時的空間交換角色,因此務必保證一個survivor空間是空的)
- 不適用與老年代中的內存回收,由於老年代中的對象的生命週期都比較長,因此會有不少的複製,效率和效果都不太好
標記-壓縮算法(Mark-Compact)
- 適用於老年代
- 當標記出垃圾對象以後,會將全部的存活對象移動到一個規整且連續的空間,而後執行Full GC(Major GC),垃圾回收以後,已用和未用的內存都各自一邊.
總結
- 新生代的GC算法一速度優先,新生代的對象生命週期都很是短暫,內存空間也比較小.因此這塊的垃圾回收會很是頻繁.
- 老年代的GC算法使用更節省內存的算法,老年代的對象生命週期都比較長,而且老年代佔了絕大部分的堆空間.
垃圾收集器
-
上面說的是JVM的內存回收算法,接下來講的是GC的具體實現.有許多的GC版本,好比:Serial/Serial Old,Parallel/Parallel Old,CMS,G1佈局
-
這些新生代和老年代的GC算法能夠自由組合 性能
-
兩個很是重要的概念:串行仍是並行回收,併發仍是"Stop-the-World"機制優化
-
串行回收簡單來講舊書多個CPU能夠用時,也只有一個CPU用於垃圾回收操做,而且在執行垃圾回收的時候,程序中的工做線程會被暫停(注意這個的區別),串行回收缺省在Client模式下的JVM.並行收集能夠運用多個CPU同時進行垃圾收集,提高了吞吐量,可是仍是要"STW",這個必定要注意.
-
第二個概念是說的是回收的時候,要不要stw的,最新的G1也作不到徹底不須要STW.
串行回收:Serial收集器
- 用於新生代,採用複製算法,串行回收和STW.是Client下的缺省新生代GC
- Serial Old 採用標記-壓縮算法,其餘同樣.
-XX:+UseSerialGC
並行回收:ParNew收集器
- 用並行方式執行內存回收,其餘和Seial幾乎沒有區別.在單個cpu下,ParNew不見得會比Serial高效
- 在某些注重低延遲的應用場景中,ParNew+CMS(Concurrent-Mark-Sweep)收集器組合執行Server模式下的內存回收幾乎是最佳的選擇.
-XX:+UseParNewGC
程序吞吐量優先:Parallel收集器
- 複製算法,並行回收和STW,
- 和ParNew最大的不通是Parallel能夠控制程序的吞吐量大小.
-XX:GCTimeRatio
設置內存回收的時間所佔JVM運行總時間的比例(也就是控制GC的執行頻率,公式爲1/(1+N),默認值是99,也就是說將只有1%的時間用於執行垃圾回收)
-XX:MaxGCPauseMillis
設置STW的暫停時間閾值,若是指定了該選項,Parallel收集器將會盡量地在指定時間範圍完成內存回收.
- 注意!吞吐量和低延遲這兩個目標實際上是存在相互競爭的矛盾,若是選擇吞吐量優先,就會下降內存回收的執行頻率,這會致使GC須要更長的暫停時間來執行垃圾回收.若是選擇以低延遲優先,那麼爲了下降每次GC的時間,只能更加頻繁的進行GC,這會致使吞吐量降低.
-XX:UseAdaptiveSizePolicy
用於甚至GC的自動分代大小調整策略,這個JVM就是自動調整相關參數
- Parallel Old採用標記-壓縮算法,Parallel+parallel Old是個不錯的選擇
低延遲:CMS(Concurrent-Mark-Sweep)
- 是優秀的老年代收集器,採用標記-清除算法,也會stw
- 有如下階段:
- 初始標記階段
- 併發標記階段
- 再次標記階段,由於工做線程和垃圾回收線程共同工做,因此須要再次標記.因此不可避免的仍是會有漏網之魚
- 併發清除階段
- 因爲採用標記-清除(通常老年代採用標記壓縮),因此不可避免的會有內存碎片,因此使用CMS爲新對象分配內存空間的時候,將沒法使用指針碰撞技術,只能選擇空閒列表進行內存分配
-XX:+UseCMSCompactAtFullCollection
,用於在執行完指定的FullGC以後是否對內存空間進行壓縮整理.
-XX:CMSFullGCsBeforeCompaction
用於設置執行多少次Full GC以後對內存空間進行壓縮整理
- 一個要糾正的點是 Full-GC是遍及整個堆空間的,不僅是在老年代中.而CMS能夠提供
-XX:CMSInitiatingOccupancyFraction
用於設置當老年代的內存使用率達到多少的時候執行內存回收.(缺省92%) 注意 這裏的內存回收僅限於老年代,因此能夠有效的下降FullGC的次數
區域分代式:G1(Garbage-First)收集器
Java虛擬機精講