Jvm 虛擬機概念
- Java 虛擬機(Jvm)是可運行 Java 代碼的假想計算機性宏,Java 虛擬機包括了一套字節(jié)碼指令集、一組寄存器(用于存儲每個線程下一條執(zhí)行的 Jvm 指令)蛇摸、一個棧好港、一個垃圾收集器和一個存儲方法域
- 每一個平臺(操作系統(tǒng))的代碼解釋器是不同的鸽嫂,但是實現(xiàn)的虛擬機(Java虛擬機接口)是相同的,這也就是為什么 Java 能夠跨平臺
Java 代碼編譯和執(zhí)行過程
- Java 源文件通過 Java 源碼編譯器生存相應(yīng)的 .class 文件(字節(jié)碼文件)
- 而字節(jié)碼通過 Jvm 中的解釋器(字節(jié)碼指令集)編譯成特定的機器碼
Jvm 生命周期
- Jvm 實例對應(yīng)了一個獨立運行的 Java 程序
- 啟動一個 Java 程序時髓窜,一個 Jvm 實例就產(chǎn)生了扇苞,main() 函數(shù)作為 Jvm 實例運行的起點
- Jvm 內(nèi)部有兩種線程:守護線程和非守護線程,main()屬于非守護線程寄纵,守護線程通常由 Jvm 自己使用鳖敷,Java 程序也可以聲明守護線程
- 當程序中的所有非守護線程都終止時,Jvm 才退出程拭,若安全管理器允許定踱,程序也可以使用 Runtime 類或者 System.exit() 退出
Jvm 內(nèi)存組成
堆
- 所有通過 new 創(chuàng)建的對象的內(nèi)存都在堆中分配,堆的大小可以通過 -Xmx 和 -Xms 來控制
- 堆內(nèi)存分為 3 部分:
- 新生代(新生代進一步劃分為 Eden恃鞋、Survivor0崖媚、Survivor1 三個區(qū)):new 出來的對象一般情況下都會在新生代里的 Eden 區(qū)里面分配空間,如果存活時間足夠長則會進入 Survivor 區(qū)
- 老年代:若新生代的對象存活更長則會被分配到老年代里
- 永久代:存放的是 Class 類元數(shù)據(jù)恤浪、方法等
棧
- 每個線程執(zhí)行每個方法的時候都會在棧中申請一個棾┭疲空間
- 每個棧空間包括局部變量區(qū)和操作數(shù)棧水由,用于存放此次方法調(diào)用過程中的臨時變量荠呐、參數(shù)和中間結(jié)果
本地方法棧
- 用于支持 native 方法的執(zhí)行,存儲了每個 native 方法調(diào)用的狀態(tài)
方法區(qū)
- 方法區(qū)域存放了所有加載的類信息(名稱、修飾符等)、類中的靜態(tài)變量芯肤、常量贪薪、類的方法信息等
- 當開發(fā)人員通過 Class 對象中的 getName 等方法獲取信息時,這些數(shù)據(jù)都來源于方法區(qū)域昧互,同時方法區(qū)域也是全局共享的
Jvm 組成
GC 垃圾回收
- Java 之所以不用像 C++ 一樣手動處理內(nèi)存的回收,是因為 JVM 虛擬機上增加了垃圾回收(GC)機制,GC 會在合適的時間觸發(fā)垃圾回收筝野,將不需要的內(nèi)存空間回收釋放,避免內(nèi)存增長導(dǎo)致 OOM(內(nèi)存泄露)
-
GC 會判斷對象是否存活確定是否回收粤剧,判斷對象是否存活一般有兩種方式:
- 引用計數(shù):每一個對象都有一個引用計數(shù)屬性歇竟,新增一個引用時計數(shù)加 1,引用釋放時減 1抵恋,計數(shù)為 0 時可以回收焕议。此方法簡單,但無法解決對象相互循環(huán)引用問題
-
可達性分析:
- 從 GC Roots 開始向下搜索弧关,搜索所走過的路徑稱為引用鏈盅安,當一個對象到 GC Roots 沒有任何引用鏈時唤锉,則證明此對象時不可用的,不可達對象
- 在現(xiàn)實情況我們往往需要通過把指向某個對象的 Reference 置空來保證這個對象在下次 GB 運行的時候被回收
Object c = new Car(); c=null;
- 可手動置空是一個很繁瑣的事情别瞭,對于簡單狀況窿祥,手動置空是不需要程序員來做的,因為對于簡單對象蝙寨,當調(diào)用它的方法執(zhí)行完畢之后晒衩,所指向它的引用會被 stack(棧)中 popup(出棧),所以它就能在下一次 GB 執(zhí)行時被回收了
垃圾收集算法
“標記-清除”
- “標記-清除”算法墙歪,算法分為“標記”和“清除”兩個階段:
- 首先標記出所有需要回收的對象
- 在標記完成后統(tǒng)一回收掉所有被標記的對象
- 之所以說它是最基礎(chǔ)的收集算法听系,是因為后續(xù)算法都是基于這種思路對其改進
- 它的缺點主要是:
- 效率問題:通過標記和清除過程效率都不高
- 空間問題:標記清除后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,導(dǎo)致程序在以后的運行過程中需要分配較大內(nèi)存對象時無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作
“復(fù)制”
- “復(fù)制”算法將可用內(nèi)存按容量劃分為大小相等的兩塊虹菲,每次只使用其中一塊靠胜,當這一塊的內(nèi)存用完了,就將還存活的對象復(fù)制到另外一塊上面毕源,然后再把已使用過的內(nèi)存空間一次性清理掉
- 這樣使得每次都是對其中一塊內(nèi)存進行回收浪漠,內(nèi)存分配時也就不用考慮內(nèi)存碎片等復(fù)雜情況,只要移動堆頂指針脑豹,按順序分配內(nèi)存即可郑藏,實現(xiàn)簡單,運行高效瘩欺。但這種算法代價是將內(nèi)存縮小為原來的一半
“標記-壓縮”
- “復(fù)制”收集算法在對象存活率較高時就要執(zhí)行較多的復(fù)制操作必盖,效率將會變低,更關(guān)鍵的是俱饿,如果不想浪費 50% 的空間歌粥,就需要額外的空間提供分配擔保,以應(yīng)對被使用的內(nèi)存中所有對象都 100% 存活的極端情況拍埠,所以一般不能直接用這種算法
- “標記-壓縮”標記過程與“標記-清除”算法一樣失驶,但后續(xù)步驟不是直接對可回收對象進行清理,而是讓所有存活對象都向一端移動枣购,然后直接清理掉端邊界以外的內(nèi)存
分代回收算法
- “分代收集”算法把 Java 堆分成新生代和老年代嬉探,這樣就可以根據(jù)各個年代的特點采用最適當?shù)氖占惴?/li>
- 在新生代中,每次垃圾收集時都發(fā)現(xiàn)有大批對象死去棉圈,只有少量的存活涩堤,主要采用復(fù)制算法,只需要付出少量存活對象的復(fù)制成本就可以完成收集
- 而老年代中因為對象存活率較高分瘾、沒有額外空間對它進行分配擔保胎围,通常采用“標記-清除”或者“標記-壓縮”算法
GC 收集器
Serial 串行收集器
- 串行收集器是最古老,最穩(wěn)定以及效率最高的收集器,可能會產(chǎn)生較長的停頓
- 只使用一個線程去回收白魂,采用分代回收算法
- 垃圾收集過程中會 Stop The World(服務(wù)暫停)
ParNew 并行收集器
- ParNew 收集器其實就是 Serial 收集器的多線程版本
Parallel 收集器
- Parallel 收集器類似 ParNew 收集器汽纤,Parallel 更關(guān)注系統(tǒng)的吞吐量,可以通過參數(shù)來打開自適應(yīng)調(diào)節(jié)策略
- 虛擬機會根據(jù)當前系統(tǒng)運行情況收集性能監(jiān)控信息福荸,動態(tài)調(diào)整這些參數(shù)以提供最適合停頓時間或最大的吞吐量
- 也可以通過參數(shù)控制 GC 時間大于多少毫秒或者比例
CMS 收集器
- CMS 收集器是一種獲取最短回收停頓時間為目標的收集器
- 目前很大一部分 Java 應(yīng)用是B/S 系統(tǒng)的服務(wù)端上蕴坪,這類應(yīng)用尤其重視服務(wù)器的響應(yīng)速度
- CMS 基于 “標記-清除” 算法實現(xiàn),它的運作過程相對復(fù)雜敬锐,分為:
- 初始標記
- 并發(fā)標記
- 重新標記
- 并發(fā)清除
- 其中初始標記辞嗡、重新標記這兩步驟仍需要 “Stop The World”
- 優(yōu)點:并發(fā)收集、低停頓
- 缺點:產(chǎn)生大量的空間碎片滞造,并發(fā)階段會降低吞吐量
G1 收集器
- G1 收集器是目前技術(shù)發(fā)展最前沿的成功之一,與CMS相比:
- 空間整合:G1 收集器采用”標記-壓縮“算法栋烤,不會產(chǎn)生內(nèi)存碎片谒养,分配大對象時不會因為找不到連續(xù)空間而提前觸發(fā)下一次 GC
- 可預(yù)測停頓:G1 追求低停頓時間外,還能建立可預(yù)測停頓時間模型明郭,能讓使用者指定在一個長度為 N 的毫秒時間片段內(nèi)买窟,消耗在垃圾收集上的時間不得超過 N 毫秒
- G1 收集器時,它將整個 Java 堆劃分為多個大小相等的區(qū)域薯定,雖然還保留新生代和老年代的概念始绍,但新生代和老年代不再是物理隔閡,他們都是一部分(可以不連續(xù))Region 的集合话侄。當新生代占用達到一定比例時亏推,開始出發(fā)收集