1.JVM與DVM
1.概念
JVM的作用是把平臺無關(guān)的.class里面的字節(jié)碼翻譯成平臺相關(guān)的機(jī)器碼,來實(shí)現(xiàn)跨平臺。DVM就是安卓中使用的虛擬機(jī)。
Dalvik允許多個(gè)實(shí)例,每一個(gè)實(shí)例作為一個(gè)獨(dú)立的linux進(jìn)程執(zhí)行,可以防止一個(gè)程序的崩潰導(dǎo)致所有程序都崩潰。
2.區(qū)別
- jvm通過解碼class文件來運(yùn)行程序;dvm則是dex文件。
dex文件是由多個(gè)class文件打包而成,一個(gè)dex文件方法數(shù)不能超過65535。
JVM: .java -> javac -> .class -> jar -> .jar , 架構(gòu): 堆和棧的架構(gòu).
DVM: .java -> javac -> .class -> dx.bat -> .dex , 架構(gòu): 寄存器(cpu上的一塊高速緩存)。
Dalvik字節(jié)碼中,變量會被復(fù)制給65536個(gè)可用寄存器中的任何一個(gè),直接訪問這些寄存器,而不是方位堆棧中的元素;
JVM字節(jié)碼中,變量會被壓入堆棧中進(jìn)行運(yùn)算;基于寄存器的方式在編譯的時(shí)候花費(fèi)的時(shí)間更短。這也是下面要說的。
- dvm基于寄存器架構(gòu)(句柄引用),jvm基于棧架構(gòu)(指針引用)
dvm基于寄存器,所以它的指令是二地址和三地址混合,指令中指明了操作數(shù)的地址;jvm基于棧,它的指令是零地址,指令的操作數(shù)對象默認(rèn)是操作數(shù)棧中的幾個(gè)位置。但基于寄存器的指令由于需要指定源地址和目標(biāo)地址,因此需要占用更多的指令空間
dvm速度快!指令數(shù)小!寄存器存取速度比棧快的多,dvm可以根據(jù)硬件實(shí)現(xiàn)最大的優(yōu)化,比較適合移動設(shè)備。JAVA虛擬機(jī)基于棧結(jié)構(gòu),程序在運(yùn)行時(shí)虛擬機(jī)需要頻繁的從棧上讀取寫入數(shù)據(jù),這個(gè)過程需要更多的指令分派與內(nèi)存訪問次數(shù),會耗費(fèi)很多CPU時(shí)間。
這樣帶來的結(jié)果就是dvm的指令數(shù)相對于jvm的指令數(shù)會小很多,jvm需要多條指令而dvm可能只需要一條指令。jvm基于棧帶來的好處是可以做的足夠簡單,真正的跨平臺,保證在低硬件條件下能夠正常運(yùn)行。而dvm操作平臺一般指明是ARM系統(tǒng),所以采取的策略有所不同。需要注意的是dvm基于寄存器,但是這也是個(gè)映射關(guān)系,如果硬件沒有足夠的寄存器,dvm將多出來的寄存器映射到內(nèi)存中。
- Dalvik可執(zhí)行文件體積更小。
SDK中有個(gè)dx工具負(fù)責(zé)將JAVA字節(jié)碼轉(zhuǎn)換為Dalvik字節(jié)碼,將所有java文件中的常量池合并為一個(gè)常量池,使得相同的字符串和常量只在DEX文件中出現(xiàn)一次。
運(yùn)行時(shí),共享相同的類,這樣系統(tǒng)消耗會小很多,JVM機(jī)制中,打包后,他們都是完全獨(dú)立的程序,類都是單獨(dú)加載,單獨(dú)運(yùn)行;
3.jvm(參考)
當(dāng)啟動一個(gè)java程序,一個(gè)虛擬機(jī)實(shí)例也就誕生了。當(dāng)該程序關(guān)閉退出,這個(gè)虛擬機(jī)實(shí)例也就隨之消亡。
java內(nèi)部有兩種線程:守護(hù)線程和非守護(hù)線程。非守護(hù)線程就是我們所說的main方法;守護(hù)線程通常是由虛擬機(jī)自己使用,如執(zhí)行垃圾回收任務(wù)的線程。Java程序也可以把它創(chuàng)建的任何線程標(biāo)記為守護(hù)線程。只要還有非守護(hù)進(jìn)行在,這個(gè)java程序也在繼續(xù)進(jìn)行。
每個(gè)Java虛擬機(jī)都有一個(gè)類裝載子系統(tǒng),它根據(jù)給定的全限定名來裝入類型(類或接口)。同樣,每個(gè)Java虛擬機(jī)都有一個(gè)執(zhí)行引擎,它負(fù)責(zé)執(zhí)行那些包含在被裝載類的方法中的指令。
每個(gè)Java虛擬機(jī)實(shí)例都有一個(gè)方法區(qū)以及一個(gè)堆,它們是由該虛擬機(jī)實(shí)例中所有的線程共享的。當(dāng)虛擬機(jī)裝載一個(gè)class文件時(shí),它會從這個(gè)class文件包含的二進(jìn)制數(shù)據(jù)中解析類型信息。然后把這些類型信息放到方法區(qū)中。當(dāng)程序運(yùn)行時(shí),虛擬機(jī)會把所有該程序在運(yùn)行時(shí)創(chuàng)建的對象都放到堆中。
當(dāng)每一個(gè)新線程被創(chuàng)建時(shí),它都將得到它自己的PC寄存器(程序計(jì)數(shù)器)以及一個(gè)Java棧。如果線程正在執(zhí)行的是一個(gè)Java方法(非本地方法),那么PC寄存器的值將總是指向下一條將被執(zhí)行的指令,java棧存儲該線程中Java方法調(diào)用的狀態(tài)——包括它的局部變量,被調(diào)用時(shí)傳進(jìn)來的參數(shù)、返回值,以及運(yùn)算的中間結(jié)果等等。而本地方法調(diào)用的狀態(tài),則是以某種依賴于具體實(shí)現(xiàn)的方法存儲在本地方法棧中,也可能是在寄存器或者其他某些與特定實(shí)現(xiàn)相關(guān)的內(nèi)存區(qū)中。
Java虛擬機(jī)沒有寄存器,其指令集使用Java棧來存儲中間數(shù)據(jù)。Java棧是由許多棧幀(stack frame)組成的,一個(gè)棧幀包含一個(gè)Java方法調(diào)用的狀態(tài)。當(dāng)線程調(diào)用一個(gè)Java方法時(shí),虛擬機(jī)壓入一個(gè)新的棧幀到該線程的Java棧中,當(dāng)該方法返回時(shí),這個(gè)棧幀被從Java棧中彈出并拋棄。
虛擬機(jī)必須為每個(gè)被裝載的類型維護(hù)一個(gè)常量池。常量池就是該類型所用常量的一個(gè)有序集合,包括直接常量和對其他類型、字段和方法的符號引用。池中的數(shù)據(jù)項(xiàng)就像數(shù)組一樣是通過索引訪問的。因?yàn)槌A砍卮鎯α讼鄳?yīng)類型所用到的所有類型、字段和方法的符號引用,所以它在Java程序的動態(tài)連接中起著核心的作用。
創(chuàng)建對象就是通過常量池中對應(yīng)類的引用找到方法區(qū)中對應(yīng)類。
4.Dalvik
Dalvik將堆分成了Active堆和Zygote堆,Zygote堆是Zygote進(jìn)程在啟動的時(shí)候預(yù)加載的類、資源和對象,除此之外的所有對象都是存儲在Active堆中的。
Dalvik的gygote堆存放的預(yù)加載的類都是Android核心類和java運(yùn)行時(shí)庫,這部分內(nèi)容很少被修改,大多數(shù)情況父進(jìn)程和子進(jìn)程共享這塊內(nèi)存區(qū)域。通常垃圾回收重點(diǎn)對Active堆進(jìn)行回收操作,Dalvik為了對堆進(jìn)行更好的管理創(chuàng)建了一個(gè)Card Table、兩個(gè)Heap Bitmap和一個(gè)Mark Stack數(shù)據(jù)結(jié)構(gòu)。
許多GC實(shí)現(xiàn)都是在對象開頭的地方留一小塊空間給GC標(biāo)記用。Dalvik VM則不同,在進(jìn)行GC的時(shí)候會單獨(dú)申請一塊空間,以位圖的形式來保存整個(gè)堆上的對象的標(biāo)記,在GC結(jié)束后就釋放該空間。
Dalvik使用即時(shí)編譯(JIT),即是在程序運(yùn)行過程中進(jìn)行編譯。JIT會在運(yùn)行時(shí)分析應(yīng)用程序的代碼,識別哪些方法可以歸類為熱方法,這些方法會被JIT編譯器編譯成對應(yīng)的匯編代碼,然后存儲到代碼緩存中,以后調(diào)用這些方法時(shí)就不用解釋執(zhí)行了,可以直接使用代碼緩存中已編譯好的匯編代碼。
javac把程序源碼編譯成JAVA字節(jié)碼,JVM通過逐條解釋字節(jié)碼將其翻譯成對應(yīng)的機(jī)器指令,逐條讀入,逐條解釋翻譯,執(zhí)行速度必然比C/C++編譯后的可執(zhí)行二進(jìn)制字節(jié)碼程序慢,為了提高執(zhí)行速度,就引入了JIT技術(shù)。
2.ART
與Dalvik不同,ART使用預(yù)編譯(AOT,Ahead-Of-Time)。也就是在APK運(yùn)行之前,就對其包含的Dex字節(jié)碼進(jìn)行翻譯,得到對應(yīng)的本地機(jī)器指令,于是就可以在運(yùn)行時(shí)直接執(zhí)行了。
ART應(yīng)用安裝的時(shí)候把dex中的字節(jié)碼將被編譯成本地機(jī)器碼,之后每次打開應(yīng)用,執(zhí)行的都是本地機(jī)器碼。去除了運(yùn)行時(shí)的解釋執(zhí)行,效率更高,啟動更快。
在Android系統(tǒng)啟動過程中創(chuàng)建的Zygote進(jìn)程利用ART運(yùn)行時(shí)導(dǎo)出的Java虛擬機(jī)接口創(chuàng)建ART虛擬機(jī)。APK在安裝的時(shí)候,打包在里面的classes.dex文件會被工具dex2oat翻譯成本地機(jī)器指令,最終得到一個(gè)ELF格式的oat文件。APK運(yùn)行時(shí),上述生成的oat文件會被加載到內(nèi)存中,并且ART虛擬機(jī)可以通過里面的oatdata和oatexec段找到任意一個(gè)類的方法對應(yīng)的本地機(jī)器指令來執(zhí)行。 oat文件中的oatdata包含用來生成本地機(jī)器指令的dex文件,內(nèi)容oat文件中的oatexec包含有生成的本地機(jī)器指令。
可以分配內(nèi)存的Space有三個(gè):Zygote Space、Allocation Space和Large Object Space。實(shí)際上應(yīng)用運(yùn)行的時(shí)候能夠分配內(nèi)存也就Allocation 和 Large Object Space兩個(gè)。
ART優(yōu)點(diǎn):
①系統(tǒng)性能顯著提升,每次啟動執(zhí)行的時(shí)候,都可以直接運(yùn)行,因此運(yùn)行效率會提高。
②應(yīng)用啟動更快、運(yùn)行更快、體驗(yàn)更流暢、觸感反饋更及時(shí)
③續(xù)航能力提升,因?yàn)閼?yīng)用程序每次運(yùn)行時(shí)不用重復(fù)編譯了,從而減少了 CPU 的使用頻率,降低了能耗。
④支持更低的硬件
ART缺點(diǎn)
①更大的存儲空間占用,可能增加10%-20%(字節(jié)碼變?yōu)闄C(jī)器碼之后,可能會增加10%-20%)
②更長的應(yīng)用安裝時(shí)間,應(yīng)用在第一次安裝的時(shí)候,字節(jié)碼就會預(yù)編譯(AOT)成機(jī)器碼,這樣的話,雖然設(shè)備和應(yīng)用的首次啟動(安裝慢了)會變慢
4.android7.0\7.1
android7.0\7.1的ART引入了全新的Hybrid模式(Interpreter + JIT + AOT)
- 與前面不同,app在安裝時(shí)不進(jìn)行編譯,所以安裝速度會更快。
- 在運(yùn)行App時(shí), 先走解釋器, 然后熱點(diǎn)函數(shù)會被識別,并被JIT進(jìn)行編譯, 存儲在jit code cache, 并產(chǎn)生profile文件(記錄熱點(diǎn)函數(shù)信息)。
- 等手機(jī)進(jìn)入充電和空閑狀態(tài)下, 系統(tǒng)會每隔一段時(shí)間掃描App目錄下profile文件,并執(zhí)行AOT編譯(Google官方稱之為profile-guided compilation)。
- 不論是jit編譯的binary code, 還是AOT編譯的binary code, 它們之間的性能差別不大, 因?yàn)樗鼈兪褂猛粋€(gè)optimizing compiler進(jìn)行編譯。
這樣的優(yōu)點(diǎn)是:App安裝速度快,占用存儲少(只編譯熱點(diǎn)函數(shù))。
缺點(diǎn)是:前幾次運(yùn)行會較慢, 只有用戶操作得次數(shù)越多,jit 和AOT編譯后, 性能才會跟上來。
后記
垃圾回收機(jī)制也是很重要的一環(huán),但這個(gè)能說的太多了。
參考:
JVM、Dalvik VM和ART虛擬機(jī)之間的區(qū)別
Dalvik虛擬機(jī)簡要介紹和學(xué)習(xí)計(jì)劃