Android系統分析之JVM/DVM、垃圾回收機制與類加載器

1 Java虛擬機

1.1 JVM(Java Virtual Machine,Java虛擬機)

  JVM的中文名稱叫Java虛擬機,它是由軟件技術模擬出計算機運行的一個虛擬的計算機。JVM也充當着一個翻譯官的角色,我們編寫出的Java程序,是不能夠被操作系統所直接識別的,由JVM負責把程序翻譯給系統“聽”,告訴它我們的程序需要做什麼操作。
  JVM在每個操作系統中有其對應的Java解釋器,解釋器會將Java程序經過編譯後產生的.Class文件解釋成特定的機器碼,被操作系統所識別,實現一次編譯到處運行

1.2 JVM的內存結構

這裏寫圖片描述
圖片來源於:Java JVM 運行機制及基本原理
(1)類加載系統(ClassLoader):在JVM啓動時或者在類運行時將需要的class加載到JVM中
(2)內存空間(也叫運行時數據區):是在JVM運行的時候所分配的內存區,運行時內存區主要可以劃分爲5個區域;
(3)執行引擎:負責執行class文件中包含的指令
(4)本地庫接口:主要是調用C或C++實現的本地方法及返回結果

1.3 JVM的生命週期

  JVM在Java程序開始執行的時候,它才運行,程序結束的時它就停止。一個Java程序會開啓一個JVM進程,如果一臺機器上運行三個程序,那麼就會有三個運行中的JVM進程。
  JVM中的線程分爲兩種:守護線程和普通線程。*守護線程是:JVM自己使用的線程,比如垃圾回收(GC)普通線程是:Java程序的線程*,只要JVM中有普通線程在執行,那麼JVM就不會停止。權限足夠的話,可以調用exit()方法終止程序。

2 JVM內存管理

  JVM的內存管理就是:內存對象的分配和釋放問題,程序員需要爲每個對象申請內存空間 (基本類型除外),對象的釋放是由GC決定和執行的。
  這種收支兩條線的方法確實簡化了程序員的工作,但也加重了JVM的工作,這也是Java程序運行速度較慢的原因之一。因爲GC爲了能夠正確釋放對象,GC必須監控每一個對象的運行狀態,包括對象的申請、引用、被引用、賦值等,GC都需要進行監控。監視對象狀態是爲了更加準確地、及時地釋放對象,而釋放對象的根本原則就是該對象不再被引用。

2.1 JVM內存分配

2.1.1 內存分配類型

  • 方法區(靜態存儲區)內存在程序編譯時就分配好了,這塊內存在程序整個運行期間都一直存在。它主要存放靜態數據和一些常量
  • 棧區方法體內的局部變量都在棧上創建,生命週期隨方法而結束。因爲棧內存分配運算內置於處理器的指令集中,效率很高,但是分配的內存容量有限。
  • 堆區:也叫動態內存,通常使用new來申請分配一個內存。包括:①全局成員變量全部存儲在堆中(包括基本數據類型,對象引用及引用的對象實體),因爲他們屬於類,類對象最終還是要被new出來的;②局部變量創建的對象存儲於堆中。GC會根據內存的使用情況,對堆內存裏的垃圾內存進行回收。
  • 本地方法棧:專門爲native方法服務的,例如:C、C++方法。
  • 程序計數器(PC Register):保存當前線程執行的內存地址。由於JVM程序是多線程執行的,所以爲了保證線程切換回來後,還能恢復到原先狀態,就需要一個獨立的計數器,記錄中斷的地方,可見程序計數器也是線程私有的。

2.1.2 棧與堆的區別

public class Main{
    int a = 1;//堆中
    Student s = new Student();//堆中    堆中
    public void XXX1(){
        int b = 1;//棧中
        Student s = new Student();//棧中    堆中
    }
    public void XXX2(){
        String s1 = new String("myString");//棧中    堆中
        String s2 = "myString";//棧中    方法區
    }
}

這裏寫圖片描述

2.1.3 結論

(1)局部變量的基本數據類型和引用,存儲於棧中,引用的對象實體存儲於堆中。因爲它們屬於方法中的變量,生命週期隨方法而結束。
(2)成員變量全部存儲與堆中(包括基本數據類型,引用和引用的對象實體),因爲它們屬於類,類對象終究是要被new出來使用的。
(3)我們所說的內存泄露,只針對堆內存,他們存放的就是引用指向的對象實體。

2.2 JVM內存釋放

  釋放對象的根本原則就是:該對象不再被引用。JVM通過GC機制(內存垃圾回收機制)來釋放回收堆和方法區中的內存,這個過程是自動執行的。GC會從根節點(GC Roots)開始對堆內存進行遍歷,到最後,沒有直接或者間接引用到GC Roots的就是需要回收的垃圾,會被GC回收掉。。GC主要完成3件事:確定哪些內存需要回收;確定什麼時候需要執行GC;如何執行GC。

2.2.1 更好理解GC的工作原理

  爲了更好理解GC的工作原理,我們可以將對象考慮爲有向圖的頂點,將引用關係考慮爲圖的有向邊,有向邊從引用者指向被引對象。另外每個線程對象可以作爲一個圖的起始頂點
  例如:大多程序從main進程開始執行,那麼該圖就是以main進程頂點開始的一棵根樹。在這個有向圖中,根頂點可達的對象都是有效對象,GC將不回收這些對象。如果某個對象 (連通子圖)與這個根頂點不可達,那麼我們認爲這個(這些)對象不再被引用,可以被GC回收。
這裏寫圖片描述
圖例演示–3分鐘瞭解:Java垃圾收集器GC 如何確定哪些是“垃圾”?

2.2.2 GC Root對象有哪些

(1)虛擬機棧中的對象引用(引用是在棧幀中的本地變量表中的),真正的對象在堆中;
(2)方法區中的類靜態對象引用
(3)方法區中的常量池對象引用
(4)本地方法棧中的JNI的對象引用

2.2.3 GC工作原理(Garbage Collection)

2.2.3.1 垃圾收集算法

(1)引用計數法:使用計數器進行內存管理。被引用1次,計數器加1,沒有被引用的時候,則回收。但是引用計數法無法解決對象之前相互引用的問題,因此已經廢棄。
(2)可達性算法(根搜索算法):有向圖的方式進行內存管理。通過GC ROOT對象開始搜索,不可達的對象則回收。這時候可以提到引用的類型,主要用得最多就是強引用和弱引用。當存在強引用的時候,內存不足寧願拋出OOM也不會回收;但是是弱引用的話,就有可能會被回收,這樣就防止了內存泄漏。

2.2.3.2 垃圾回收算法

(1)標記-清除算法:搜索,發現沒有引用的對象,直接回收,但是會導致內存碎片過多。
這裏寫圖片描述
(2)複製算法:搜索,掃描沒有引用的對象。開闢新的內存空間,將存活的對象複製到新的內存,舊的內存直接清除。由於需要多次交換內存空間,因此在對象數量比較少的時候效率比較高。
這裏寫圖片描述
(3)標記-整理算法:在標記-清除算法的基礎上,清除掉不存活的對象之後,把後面的存活對象搬移過來,似的內存連續,解決了內存碎片的問題。
這裏寫圖片描述
(4)三種算法是混合使用的,不同情況,例如對象數量不同,採用不用的算法,已達到最大的效率。

2.2.4 觸發垃圾回收方式

(1)GC_CONCURRENT:當我們應用程序的堆內存快要滿的時候,系統會自動觸發GC操作來釋放內存
(2)GC_FOR_MALLOC:當我們的應用程序需要分配更多內存,可是現有內存已經不足的時候,系統會進行GC操作來釋放內存
(3)GC_HPROF_DUMP_HEAP:當生成Hprof文件的時候,系統會進行GC操作
(4)GC_EXPLICIT:主動通知系統去進行GC操作,比如調用System.gc()方法來通知系統。或者在Android Monitor中,通過工具按鈕告訴系統進行GC操作的。

3 類加載器(需要補充)

3.1 JVM的類加載器

  Android的類加載器跟原生的類加載器不一樣,但是都大同小異:
這裏寫圖片描述

3.2 類加載流程

這裏寫圖片描述

3.3 類的加載過程

3.3.1 分析對象引用與對象

public class Demo{  
    public Demo{}  
}  
Demo demo = new Demo();
//可以寫成
Demo demo;//創建對象引用  
demo=/*將對象引用指向對象*/new Demo();//創建對象

(1)右邊的“new Demo”,創建一個Demo對象,存儲在堆內存中。
(2)末尾的()意味着:在對象創建後,立即調用Demo類的構造函數,對對象進行初始化。
(3)左邊的“Demo demo”聲明瞭Demo類引用變量,存儲在棧內存中。
(4)“=”操作符使對象引用指向剛創建的Demo對象。
這裏寫圖片描述
圖片來源於:java–對象引用與對象的區別

3.3.2 Person person = new Person()爲例進行說明類加載過程

1.首先虛擬機讀取指定的路徑下的Person.class文件,並加載至內存(如果該對象有直接父類則會先加載父類)—-方法區
2.按順序執行父類的static代碼塊和static變量,再執行子類的static代碼塊和static變量 —-方法區
3.創建Person對象,在堆內存開闢空間分配堆內存地址 —-堆中
4.將父類對象的屬性和代碼塊默認初始化(int類型爲0,String類型爲null);對父類對應的構造函數進行初始化 —-棧中
5.將子類對象的屬性和代碼塊默認初始化(int類型爲0,String類型爲null);對子類對應的構造函數進行初始化 —-棧中
6.進行子類構造函數的特定初始化例如聲明賦值變量(這種情況較少) —-棧中
7.聲明一個person對象引用—-棧中
8.初始化完畢,棧中的person對象引用指向堆內存中Person對象。
參考鏈接:Person p=new Person()的感悟

4 Dalvik VM(DVM)

4.1 DVM

  Dalvik是Google公司自己設計用於Android平臺的Java虛擬機。dex格式是專爲Dalvik應用設計的一種壓縮格式,適合於內存和處理器速度有限的系統Dalvik允許同時運行多個虛擬機的實例,並且每一個應用作爲獨立的Linux進程執行。獨立的進程可以防止在虛擬機崩潰的時候所有程序都被關閉
  (1)Dalvik指令集是基於寄存器的架構dex字節碼更適合於內存和處理器速度有限的系統允許同時運行多個虛擬機的實例
  (2)而JVM是基於棧的。執行的是class文件

4.2 ART虛擬機是DVM的進化版本

  2014年6月谷歌I/O大會,Android L 改動幅度較大,Google將直接刪除Dalvik,代替它的是傳聞已久的ART。
  (1)在Dalvik下,應用每次運行都需要通過即時編譯器(JIT)將字節碼轉換爲機器碼,即每次都要編譯加運行,這雖然會使安裝過程比較快,但是會拖慢應用以後每次啓動的效率
  而在ART 環境中,應用在第一次安裝的時候,字節碼就會預編譯(AOT)成機器碼,雖然設備和應用的安裝會變慢,但是以後每次啓動執行的時候,都可以直接運行,因此運行效率會提高
  (2)ART佔用空間比Dalvik大(字節碼變爲機器碼之後,可能會增加10%-20%),這也是著名的“空間換時間大法”。
  (3)預編譯也可以明顯改善電池續航,因爲應用程序每次運行時不用重複編譯了,從而減少了 CPU 的使用頻率,降低了能耗。

5 參考鏈接

虛擬機

類加載

Android類加載器ClassLoader

學習Java的內存分配機制和內存泄漏問題

JVM內存管理及GC機制

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