【Android】Android Dalvik&JVM

1、JVM

JVM,Java虛擬機,是Java運行時環境JRE的核心組成部分,這也是Java跨平臺的基礎,將Java字節碼在虛擬機中執行。

1)特點

基於棧而不是基於寄存器;基本數據類型外的其它數據類型是基於符號的引用,而不是基於內存地址的引用;GC,提供了垃圾自動回收機制;基本數據類型的長度明確定義,與平臺無關,而不像C的基本數據類型在不同的平臺上長度可能不同;字節碼採用網絡字節序,即大端表示。

2)字節碼

Java字節碼是部署Java程序的最小單元,是Java和機器語言的中間語言。
例如下面的Hello.java文件,通過javac命令編譯後生成Hello.class文件,這是個二進制文件,可通過javap命令進行反編譯。

// Hello.java
public class Hello {
	public void foo() {

	}

	public void bar(int i) {

	}

	public int hello(String s) {
		return 0;
	}
}
javac Hello.java
javap Hello.class
Compiled from "Hello.java"
public class Hello {
  public Hello();
  public void foo();
  public void bar(int);
  public int hello(java.lang.String);
}

javap還可以添加一些參數,輸出詳細的反編譯信息,這裏就不列舉了。

3)Java代碼運行流程

第一步,使用javac命令編譯java文件生成對應的class文件。
第二步,運行時ClassLoader加載class到內存,轉爲Class對象。
第三步,執行引擎根據字節碼運行程序。

4)類加載器分類

類加載器分爲以下四類。
啓動類加載器Bootstrap ClassLoader:這個類加載器負責放在<JAVA_HOME>/lib目錄中的,或者被-Xbootclasspath參數所指定的路徑中的,並且是虛擬機識別的類庫,用戶無法直接使用。
擴展類加載器Extension ClassLoader:這個類加載器由sun.misc.Launcher$AppClassLoader實現,它負責<JAVA_HOME>/lib/ext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的所有類庫,用戶可以直接使用。
應用程序類加載器Application ClassLoader:這個類加載器由sun.misc.Launcher$AppClassLoader實現,是ClassLoader中getSystemClassLoader方法的返回值,它負責用戶路徑ClassPath所指定的類庫,用戶可以直接使用。如果用戶沒有自己定義類加載器,默認使用這個。
自定義加載器User ClassLoader:用戶自己定義的類加載器。

5)類加載器特點

Java提供了動態加載的特性,只有在運行時第一次遇到類時纔會去加載和鏈接。每個類加載器都有自己的空間,用於存儲其加載的類信息。Java的類加載器按是父子關係的層次結構組織的。當一個類需要被加載,會先去請求父加載器判斷該類是否已經被加載,如果父類加器已加載了該類,那它就可以直接使用而無需再次加載,如果尚未加載,直到Bootstrap都未加載,才需要當前類加載器來加載此類。子類加載器可以從父類加載器中獲取類,反之則不行。類加載器可以載入類卻不能卸載它,但是可以通過刪除類加載器的方式卸載類。

6)類加載步驟

類加載包括如下五個步驟。
加載(Loading): 從文件中獲取類並載入到JVM內存空間。
驗證(Verifying): 驗證載入的類是否符合Java語言規範和JVM規範。在類加載流程的測試過程中,這一步是最爲複雜且耗時最長的部分。大部分JVM TCK的測試用例都用於檢測對於給定的錯誤的類文件是否能得到相應的驗證錯誤信息。
準備(Preparing): 根據內存需求準備相應的數據結構,並分別描述出類中定義的字段、方法以及實現的接口信息。
解析(Resolving): 把類常量池中所有的符號引用轉爲直接引用。
初始化(Initializing): 爲類的變量初始化合適的值。執行靜態初始化域,併爲靜態字段初始化相應的值。

7)運行時數據區

運行時數據包括如下五個組件。
方法區(Method Area):所有的類級數據將存儲在這裏,包括靜態變量。每個JVM只有一個方法區,它是一個共享資源。
堆區域(Heap Area):所有對象及其對應的實例變量和數組將存儲在這裏。每個JVM也只有一個堆區域。由於方法和堆區域共享多個線程的內存,所存儲的數據不是線程安全的。
堆棧區(Stack Area):對於每個線程,將創建單獨的運行時堆棧。對於每個方法調用,將在堆棧存儲器中產生一個條目,稱爲堆棧幀。所有局部變量將在堆棧內存中創建。堆棧區域是線程安全的,因爲它不共享資源。堆棧框架分爲三個子元素,局部變量數組(Local Variable Array),與方法相關,涉及局部變量,並在此存儲相應的值操作數堆棧(Operand stack),如果需要執行任何中間操作,操作數堆棧將充當運行時工作空間來執行操作;幀數據(Frame Data),對應於方法的所有符號存儲在此處,在任何異常的情況下,捕獲的區塊信息將被保持在幀數據中。
PC寄存器(PC Registers):每個線程都有單獨的PC寄存器,用於保存當前執行指令的地址。一旦執行指令,PC寄存器將被下一條指令更新。
本地方法堆棧(Native Method stacks):本地方法堆棧保存本地方法信息。對於每個線程,將創建一個單獨的本地方法堆棧。

8)執行引擎

分配給運行時數據區的字節碼將由執行引擎執行,執行引擎讀取字節碼並逐個執行。執行引擎包括解釋器、JIT編譯器和GC。
解釋器:解釋器解釋字節碼的速度更快,但執行速度較慢。解釋器的缺點是,當多次調用一種方法時,每次都需要新的解釋。
JIT編譯器 :JIT編譯器中和了解釋器的缺點。執行引擎將在轉換字節碼時使用解釋器的幫助,但是當它發現重複的代碼時,它將使用JIT編譯器,它編譯整個字節碼並將其更改爲本地代碼。該本機代碼將直接用於重複的方法調用,這可以提高系統的性能。JIT編譯器包括中間代碼生成器、代碼優化器、目標代碼生成器、Profiler。中間代碼生成器用於生成中間代碼;代碼優化器負責優化上面生成的中間代碼;目標代碼生成器負責生成機器代碼或本地代碼;Profiler是一個特殊的組件,負責查找熱點,即該方法是否被多次調用。
GC:收集並刪除未引用的對象。垃圾回收可以通過調用“System.gc()”觸發,但執行不能保證。JVM的垃圾回收所創建的對象。

9)GC原理

內存主要被分爲三塊:新生代(Youn Generation)、舊生代(Old Generation)、持久代(Permanent Generation)。三代的特點不同,造就了他們使用的GC算法不同,新生代適合生命週期較短,快速創建和銷燬的對象,舊生代適合生命週期較長的對象,持久代在Sun Hotpot虛擬機中就是指方法區(有些JVM根本就沒有持久代這一說法)。
新生代(Youn Generation):大致分爲Eden區和Survivor區,Survivor區又分爲大小相同的兩部分:From Space和To Space。新建的對象都是從新生代分配內存,Eden區不足的時候,會把存活的對象轉移到Survivor區。當新生代進行垃圾回收時會出發Minor GC(也稱作Youn GC)。
舊生代(Old Generation):舊生代用於存放新生代多次回收依然存活的對象,如緩存對象。當舊生代滿了的時候就需要對舊生代進行回收,舊生代的垃圾回收稱作Major GC(也稱作Full GC)。
目前爲止,JVM已經發展處四種比較成熟的垃圾收集算法:標記-清除算法、複製算法、標記-整理算法、分代收集算法。
標記-清除算法:這種垃圾回收一次回收分爲兩個階段:標記、清除。首先標記所有需要回收的對象,在標記完成後回收所有被標記的對象。這種回收算法會產生大量不連續的內存碎片,當要頻繁分配一個大對象時,JVM在新生代中找不到足夠大的連續的內存塊,會導致JVM頻繁進行內存回收(目前有機制,對大對象,直接分配到老年代中)。
複製算法:這種算法會將內存劃分爲兩個相等的塊,每次只使用其中一塊。當這塊內存不夠使用時,就將還存活的對象複製到另一塊內存中,然後把這塊內存一次清理掉。這樣做的效率比較高,也避免了內存碎片。但是這樣內存的可使用空間減半,是個不小的損失。
標記-整理算法:這是標記-清除算法的升級版。在完成標記階段後,不是直接對可回收對象進行清理,而是讓存活對象向着一端移動,然後清理掉邊界以外的內存
分代收集算法:當前商業虛擬機都採用這種算法。首先根據對象存活週期的不同將內存分爲幾塊即新生代、老年代,然後根據不同年代的特點,採用不同的收集算法。在新生代中,每次垃圾收集時都有大量對象死去,只有少量存活,所以選擇了複製算法。而老年代中因爲對象存活率比較高,所以採用標記-整理算法(或者標記-清除算法)

10)引用類型

強引用:new出來的對象都是強引用,GC無論如何都不會回收,即使拋出OOM異常。
軟引用:只有當JVM內存不足時纔會被回收。
弱引用:只要GC,就會立馬回收,不管內存是否充足。
虛引用:虛引用是每次垃圾回收的時候都會被回收,通過虛引用的get方法永遠獲取到的數據爲null,因此也被成爲幽靈引用。虛引用主要用於檢測對象是否已經從內存中刪除。

11)JVM結構

在這裏插入圖片描述

2、Dalvik

Dalvik是一種JVM實現,但並未完全遵循JVM規範,基於寄存器,Java字節碼被轉換爲Dalvik用的寄存器指令集即dex文件,佔用的內存空間更小,dex還會進一步被優化成odex。

3、ART

ART是Dalvik的替代品,ART 的機制與 Dalvik 不同。在Dalvik下,應用每次運行的時候,字節碼都需要通過即時編譯器(just in time ,JIT)轉換爲機器碼,這會拖慢應用的運行效率,而在ART 環境中,應用在第一次安裝的時候,字節碼就會預先編譯成機器碼,使其成爲真正的本地應用,這個過程叫做預編譯(AOT,Ahead-Of-Time)。這樣的話,應用的啓動(首次)和執行都會變得更加快速。
可以通過調用 System.getProperty(“java.vm.version”)來檢測當前使用的是哪個虛擬機,如果使用的是ART虛擬機的話,屬性值會大於等於2.0.0。
通過反射調用SystemProperty的get方法來查看persist.sys.dalvik.vm.lib屬性值,Android系統提供了一個系統屬性persist.sys.dalvik.vm.lib,它的值要麼等於libdvm.so要麼等於libart.so。當等於libdvm.so時,就表示當前用的是Dalvik虛擬機,而當等於libart.so時,就表示當前用的是ART虛擬機。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章