繼續(xù)做知識點鋪墊求橄。
我這里再簡單梳理下虛擬機相關知識點今野,當然只是熱修復與插件化會涉及到的部分。
一罐农、JVM整體結構
java文件先通過編譯器生成虛擬機執(zhí)行的字節(jié)碼文件条霜,該文件被ClassLoader加載到內存,由虛擬機分區(qū)域進行內存管理涵亏,然后子系統(tǒng)會執(zhí)行相關的工作:包括將虛擬機字節(jié)碼編譯為機器碼宰睡、針對堆內存進行GC等等蒲凶。同時虛擬機通過本地庫接口連接native方法與本地方法庫。
1.1 編譯流程
class文件生成是通過javac編譯器程序執(zhí)行得到的拆内,整個編譯流程簡單總結如下:
源代碼->詞法分析器->Token流->語法分析器->語法樹/抽象語法樹->語義分析器->
注解抽象語法樹->字節(jié)碼生成器->jvm字節(jié)碼
這個部分就不管了旋圆,了解下點到為止,感興趣的可以自行研究麸恍。
1.2 類加載流程
類加載器介紹:
Bootstrap Loader
負責加載系統(tǒng)類灵巧;
Extension ClassLoader
負責加載擴展類(就是繼承類和現類);
System ClassLoader
負責加載用戶類或南。
(類加載器Andorid有比較大的區(qū)別孩等,但是思想還是一樣的)
這里牽涉到幾個機制:
全盤負責
:當一個類加載器負責加載某個Class時,該Class所依賴的和引用的其他Class也將由該類加載器負責載入采够,除非顯示使用另外一個類加載器來載入肄方。雙親委派
:先判斷當前類加載器是否加載過,如果沒有則傳遞到父類類加載器中蹬癌,父類加載器沒有搜到該Class無法完成該加載权她,子加載器才會嘗試自己去加載該Class。緩存機制
:緩存機制將會保證所有加載過的Class都會被緩存逝薪,當程序中需要使用某個Class時隅要,類加載器先從緩存區(qū)尋找該Class,只有緩存區(qū)不存在董济,系統(tǒng)才會讀取該類對應的二進制數據步清,并將其轉換成Class對象,存入緩存區(qū)虏肾。
如何判斷加載的是同一個類:類名相同廓啊、包名相同、由相同的ClassLoader加載封豪。
類加載的過程:
-
Loading
:類的信息從文件中獲取并加載到JVM內存中谴轮。 -
Verifying
:檢查讀入的結構是否符合JVM規(guī)范。 -
Preparing
:驗證完正確吹埠,會分配一個結構來存儲類信息第步。 -
Resolving
: 把這個類的常量池中的所有符號引用轉變?yōu)橹苯右谩?/li> -
Initializing
:執(zhí)行靜態(tài)初始化程序,把靜態(tài)變量初始化成指定的值缘琅。
1.3 內存管理
1.3.1 內存區(qū)域劃分
-
程序計數器
:一個指針粘都,記錄當前線程所執(zhí)行到的字節(jié)碼行號; -
虛擬機棧
:存放方法執(zhí)行時的所有數據胯杭; -
本地方法棧
:專門為native方法服務的驯杜; -
方法區(qū)
:存儲被虛擬機加載的類信息、常量做个、靜態(tài)變量鸽心、及時編譯器編譯后等數據; -
java堆
: 所有new創(chuàng)建的對象的內存都在堆中分配居暖。是虛擬機中最大的一塊內存顽频,是GC要回收的部分。
線程共享區(qū)包括:方法區(qū) 太闺、java堆糯景。
線程隔離區(qū)包括:虛擬機棧、本地方法棧省骂、程序計數器蟀淮。
1.3.2 虛擬機棧解析
以線程為單位由棧結構來進行管理。棧中對應的棧元素叫棧幀钞澳,它是用于支持虛擬機進行方法調用和方法執(zhí)行的數據結構怠惶,每個方法從調用到執(zhí)行完成就對應一個棧幀在虛擬機棧中入棧到出棧的過程。棧幀包含的內容:局部變量表轧粟、棧操作數策治、動態(tài)鏈接、方法出口兰吟。
1.3.3 java堆區(qū)解析
java堆是 JVM 所管理的最大的一塊內存空間通惫,主要用于存放各種類的實例對象。
在 Java 中混蔼,堆被劃分成兩個不同的區(qū)域:新生代 ( Young )履腋、老年代 ( Old )。新生代 ( Young ) 又被劃分為三個區(qū)域:Eden惭嚣、From Survivor遵湖、To Survivor。
簡單看下區(qū)域的劃分以及虛擬機默認分配的比例:
另外還有個永久代料按,這部分屬于方法區(qū)奄侠,就捎帶提一嘴。
那么载矿,這個比例可以配置嗎垄潮?當然可以,如果玩java服務器的話闷盔,肯定要玩玄學調參弯洗。參考如下JVM參數選項:
參數名 | 介紹 |
---|---|
-Xms | 初始堆大小。如:-Xms256m |
-Xmx | 最大堆大小逢勾。如:-Xmx512m |
-Xmn | 新生代大小牡整。通常為 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 個 Survivor 空間溺拱。實際可用空間為 = Eden + 1 個 Survivor逃贝,即 90% |
-Xss | JDK1.5+ 每個線程堆棧大小為 1M谣辞,一般來說如果棧不是很深的話, 1M 是絕對夠用了的沐扳。 |
-XX:NewRatio | 新生代與老年代的比例泥从,如 –XX:NewRatio=2,則新生代占整個堆空間的1/3沪摄,老年代占2/3 |
-XX:SurvivorRatio | 新生代中 Eden 與 Survivor 的比值躯嫉。默認值為 8。即 Eden 占新生代空間的 8/10杨拐,另外兩個 Survivor 各占 1/10 |
-XX:PermSize | 永久代(方法區(qū))的初始大小 |
-XX:MaxPermSize | 永久代(方法區(qū))的最大值 |
-XX:+PrintGCDetails | 打印 GC 信息 |
-XX:+HeapDumpOnOutOfMemoryError | 讓虛擬機在發(fā)生內存溢出時 Dump 出當前的內存堆轉儲快照祈餐,以便分析用 |
jvm 可配置的參數選項可以參考 Oracle 官方網站給出的相關信息:http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html
堆內存管理規(guī)則:
新生代
:
新創(chuàng)建的對象先被放在Eden,Eden滿了哄陶,執(zhí)行Minor GC帆阳,同時把未被 GC 的對象 移動到 S0(from) 或 S1(to) 中。 最后使 S0(from) 奕筐、S1(to) 其中一個置為空舱痘,這是由GC算法決定的,因為Minor GC對應的是復制算法离赫,因此需要內存交換區(qū)芭逝,所以多分了from和to這兩塊區(qū)域。
老年代
:
內存區(qū)存放了經過多次 Minor GC 后仍然不能被 GC 的對象渊胸。Old區(qū)滿了旬盯,執(zhí)行Major GC。
另外要補充一點的是:新生代和新生代都可以主動觸發(fā) stop-the-world 事件翎猛,掛起所有任務胖翰,執(zhí)行 GC 操作。 被掛起的任務只有在 GC 執(zhí)行完畢后切厘,才會恢復執(zhí)行萨咳。
1.3.4 垃圾回收
垃圾收集算法:
引用計算算法(jdk1.2之前)
:堆中的每個對象對應一個引用計數器,創(chuàng)建對象置為1疫稿,每次引用到此對象+1培他,其中一個引用銷毀-1,變?yōu)?即滿足回收遗座。致命缺點:循環(huán)引用的對象無法進行回收舀凛。-
可達性算法(jdk1.2之后)
:確定GC root,尋找路徑可達的引用節(jié)點途蒋,形成可達性樹猛遍,不在樹上的節(jié)點即滿足回收條件。
在Java語言里,可作為GC Roots對象的包括如下幾種:- 虛擬機棧(棧楨中的本地變量表)中的引用的對象懊烤;
- 方法區(qū)中的類靜態(tài)屬性引用的對象梯醒;
- 方法區(qū)中的常量引用的對象;
- 本地方法棧中JNI的引用的對象奸晴;
引用類型:
-
強引用(StrongReference)
:JVM 寧可拋出 OOM 冤馏,也不會GC日麸; -
軟引用(SoftReference)
:只有在內存空間不足時寄啼,才會被回收; -
弱引用(WeakReference)
:在 GC 時代箭,一旦發(fā)現了只具有弱引用的對象墩划,不管當前內存空間足夠與否,都會回收它的內存嗡综; -
虛引用(PhantomReference)
:主要用來判斷對象是否將要被回收乙帮,屬于GC回收標志。
垃圾回收算法:
標記-清除算法(Tracing Collector)
:遍歷所有的GC Roots极景,然后將所有GC Roots可達的對象標記為存活的對象察净。對沒標記的對象全部清除。
優(yōu)點:對不存活對象進行處理盼樟,在存活對象高的情況下非常高效氢卡。
缺點:清除對象不會整理,造成內存碎片晨缴,這部分內存碎片屬于內部碎片译秦。
復制算法(Coping Collector)
: 遍歷所有的GC Roots,將可達的對象復制到另一塊內存空間,遍歷完后清空原來的內存空間(剩下的都是不可達對象)击碗。
優(yōu)點:對可達對象進行復制筑悴,在存活的對象比較少時極為高效。
缺點:需要額外的內存空間稍途。
標記-整理算法(Compacting Collector)
:在標記-清除算法基礎上阁吝,增加存活對象內存整理。
優(yōu)點:不造成內存碎片械拍,也不需要額外內存空間
缺點:整理過程耗時突勇,效率不高。
因此內前面咱們提到的兩個GC:
Minor GC
: 是發(fā)生在新生代中的垃圾收集動作殊者,通常對象存活時間較短与境,因此采用的是復制算法。
Full GC
: 是發(fā)生在老年代的垃圾收集動作猖吴,通常對象存活時間較長摔刁,因此采用的是標記-清除算法(考慮執(zhí)行效率)/標記-整理算法(考慮內存利用率)。
GC觸發(fā)時機:
- Java虛擬機無法再為新的對象分配內存空間了海蔽;
- 手動調用System.gc()方法(強烈不推薦:即使手動調了也不會立馬回收共屈,還會加大虛擬機壓力)绑谣;
- 低優(yōu)先級的GC線程,被調度時拗引。
二借宵、JVM與DVM不同
- 執(zhí)行文件不同,JVM對應class矾削,DVM對應dex壤玫。dex文件更精簡。
- 虛擬機架構不同哼凯,JVM基于棧欲间,DVM基于寄存器。寄存器比是內存更快的存儲介質断部,因此DVM運行更快猎贴。
- 類加載系統(tǒng)區(qū)別,JVM基于Bootstrap Loader蝴光、Extension ClassLoader她渴、App ClassLoader,DVM基于ClassLoader蔑祟、BaseDexClassLoader趁耗、PathClassLoader、DexClassLoader做瞪。
- 項目中虛擬機數目不同对粪,JVM只能同時存在1個,DVM存在多個一個進程對應一個装蓬。
三著拭、ART與DVM相比較的優(yōu)勢
編譯優(yōu)化
DVM使用JIT來將字節(jié)碼編譯為機器碼,ART引入AOT預編譯牍帚。JIT生成的機器碼緩存在內存中儡遮,優(yōu)化解釋模式的執(zhí)行,屬于運行時優(yōu)化暗赶。而OAT生成的機器碼緩存為文件鄙币,屬于持久化優(yōu)化,因此下次執(zhí)行肯定是AOT的方式更快蹂随,不用重新生成機器碼十嘿,直接拿來用。
但是OAT的缺點是每次編譯dex2oat CPU占用率非常高岳锁,可能造成部分任務搶占不到CPU绩衷。另外ART安裝時間更長,存儲空間占用更大。
垃圾回收優(yōu)化
改善了Dalvik GC流程咳燕,將其非并發(fā)過程改變成了部分并發(fā)勿决。縮短了任務掛起時間招盲,據官方測試數據說gc效率提高2倍低缩。
內存優(yōu)化
ART對比DVM 提高了內存使用率,減少了內存碎片化曹货。
之前寫的相關文章:
虛擬機(一)-JVM執(zhí)行java代碼流程淺析
虛擬機(二)-Dalvik執(zhí)行java代碼流程淺析
虛擬機(三)-JVM 咆繁、DVM 、ART簡單對比
虛擬機(四)-JVM垃圾回收