本文不對JVM 、DVM(dalvik) 、ART這三者作具體的分析。只是從內存管理的角度來介紹下三者的區別和聯繫。java
Java是一種編譯+解釋的語言。最主要的目的是跨平臺,爲了實現跨平臺,就決定了不能像 c,c++ 那樣直接把源代碼編譯成可執行文件,由於不一樣cpu,不一樣操做系統的指令封裝格式是不同的。java編譯成的字節碼文件與硬件和操做系統無關,這是跨平臺基礎,而後具體執行,再用各自平臺解釋器,解釋成本地機器碼。android
JVM
JVM本質上就是一個軟件,是計算機硬件的一層軟件抽象,在這之上纔可以運行Java程序,JAVA在編譯後會生成相似於彙編語言的.class字節碼文件,與C語言編譯後產生的彙編語言不一樣的是,C編譯成的彙編語言會直接在硬件上跑,但JAVA編譯後生成的.class字節碼是在JVM上跑,須要由JVM把字節碼翻譯成機器指令,才能使JAVA程序跑起來。c++
JVM運行在操做系統上,屏蔽了底層實現的差別,從而有了JAVA吹噓的平臺獨立性和Write Once Run Anywhere。根據JVM規範實現的具體虛擬機有幾十種,主流的JVM包括Hotspot、Jikes RVM等,都是用C/C++和彙編編寫的,每一個JRE編譯的時候針對每一個平臺編譯,所以下載JRE(JVM、Java核心類庫和支持文件)的時候是分平臺的,JVM的做用是把平臺無關的.class裏面的字節碼翻譯成平臺相關的機器碼,來實現跨平臺。web
Java程序執行流程:算法
從上圖能夠看到Java虛擬機與java語言沒有什麼必然聯繫,它只與特定的二進制文件:Class文件有關。數組
數據類型
Java虛擬機與Java語言的數據類型類似,能夠分爲兩類:基本類型和引用類型。Java虛擬機但願編譯器在編譯期間儘量的完成類型檢查,使得虛擬機在運行期間無需進行類型檢查操做。緩存
運行時數據區域
Java虛擬機在執行Java程序的過程當中會把它所管理的內存劃分爲不一樣的數據區域,數據區域分別爲微信
程序計數器架構
Java虛擬機棧jvm
本地方法棧
Java堆
方法區
1. 程序計數器
程序計數器(Program Counter Register),能夠看作當前線程所執行的字節碼的行號指示器(其實就是記錄代碼執行到了哪裏)。
特色以下:
線程私有;
佔用內存空間較小;
若線程執行的是 Java 方法,記錄的是虛擬機字節碼指令地址;若執行的是本地(Native)方法,則爲空(Undefined);
該區域是惟一一個在《Java 虛擬機規範》中規定無任何 OutOfMemoryError 的區域。
主要做用:記錄線程執行到了哪裏。
2. Java 虛擬機棧
Java 虛擬機棧(Java Virtual Machine Stacks):Java 方法執行的線程內存模型。
每一個方法被執行時,虛擬機棧都會建立一個棧幀(Stack Frame)用於存儲局部變量表、操做數棧、動態鏈接、方法出口等信息。每一個方法從被調用直至執行完畢的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。其中局部變量表包括:
Java 虛擬機基本數據類型(8 種)
對象引用(reference 類型,多是一個指向對象起始地址的指針)
returnAddress
這些數據類型在局部變量表中的存儲空間以局部變量槽(Slot)表示,其中 long 和 double 佔用兩個槽,其餘類型佔用一個槽。局部變量表所需內存空間在編譯期完成分配,當進入一個方法時,該方法須要在棧幀中分配多大的局部變量空間是徹底肯定的,運行期間不會改變其大小。
虛擬機棧的特色:
線程私有;
生命週期與線程相同;
兩類異常
線程請求的棧深度大於虛擬機所容許的深度時拋出 StackOverflowError 異常;
棧擴展時沒法申請到足夠的內存時拋出 OutOfMemoryError 異常。
主要目的:Java 方法執行的線程內存模型。
3. 本地方法棧
本地方法棧(Native Method Stacks)與 Java 虛擬機棧做用相似。兩者區別:
Java 虛擬機棧爲 JVM 執行 Java 方法(字節碼)服務;
本地方法棧爲 JVM 使用到的本地(Native)方法服務。
異常與 Java 虛擬機棧相同。
主要目的:Native 方法執行的線程內存模型。
4. Java 堆 對多數應用來講,Java 堆(Java Heap)是 JVM 管理的內存中最大的一塊。
惟一目的:存放對象實例(【幾乎全部】的對象實例都在這裏分配內存)。
《Java 虛擬機規範》描述:全部對象實例及數組都應在堆上分配。
而從實現角度看,因爲即便編譯技術(尤爲是逃逸分析技術的日漸強大),"棧上分配"等手段使得對象並不是徹底在堆上分配。
特色:
線程共享
虛擬機啓動時建立
PS: "新生代"、"老年代"、"Eden 區"等一系列對堆的區域劃分,只是部分垃圾收集器的一些共性或設計風格,而非虛擬機的固有內存佈局,更非《Java 虛擬機規範》的劃分。
將 Java 堆細分的目的只是爲了更好地回收內存,或者更快地分配內存。
5. 方法區
方法區(Method Area):用於存儲已被虛擬機加載的類型信息、常量、靜態變量、即時編譯器編譯後的代碼緩存等數據,該區域也是線程共享的。又稱"非堆"。
與方法區聯繫密切的一個概念是"永久代",下面簡要介紹。
永久代
"永久代(Permanent Generation)",能夠理解爲 JDK 1.8 以前 HotSpot 虛擬機對《Java 虛擬機規範》中"方法區"的實現。從 JDK 1.六、1.7 到 1.8+,HotSpot 虛擬機的運行時數據區變遷示意圖以下:
HotSpot VM JDK 1.6 的運行時數據區示意圖以下:
JDK 1.7 中,將 1.6 中永久代的字符串常量池和靜態變量等移到了堆中,以下(虛線框表示已移除):
而到了 JDK 1.8,則徹底廢棄了"永久代",改用了在本地內存中實現的"元空間(Metaspace)",將 JDK 1.7 中永久代剩餘的部分(主要是類型信息)移到了元空間,以下(虛線框表示已移除):
從上面幾張圖能夠看出永久代和元空間的主要區別有如下兩點:
永久代是 JVM 內存的一部分,元空間在本地內存中(JVM 內存以外);
永久代使用不當可能致使 OOM,元空間通常不會。
存儲位置不一樣
存儲內容不一樣:元空間存儲的是「類型信息」(即類的元信息),而永久代除了類型信息,還包括「字符串常量池」和「靜態變量」等(能夠理解爲元空間是永久代拆分出來的一部分)。
那麼問題來了:爲何要把永久代替換爲元空間呢?
緣由大概有如下幾點:
Oracle 收購了兩種 JVM:HotSpot VM 和 JRockit VM,而且想要將它們整合,但兩者方法區實現差別較大;
字符串存在永久代中,容易出現性能問題和 OOM;
類及方法的信息大小較難肯定,永久代大小難以肯定:過小易致使永久代溢出,太大則易致使老年代溢出(JVM 內存是有限的,此消彼長);
永久代會爲垃圾回收帶來沒必要要的複雜度,且回收效率較低("性價比"低)。
如下6 7 不在jvm內存運行模型中,但對內存管理過程當中須要注意,由於他們常常致使oom。
6. 運行時常量池 運行時常量池(Runtime Constant Pool)是方法區的一部分。
Class 文件中除了有類的版本、字段、方法、接口等描述外信息,還有一項信息是常量池表(Constant Pool Table),用於存放編譯期生成的各類字面量和符號引用,這部份內容將在類加載後進入方法區的運行時常量池中存放。
相比於 Class 文件常量池的一個重要特性是「動態性」,運行期間也能夠將新的常量放入池中(例如 String 類的 intern() 方法)。
可能產生的異常:OutOfMemoryError。
7. 直接內存 直接內存(Direct Memory)並不是虛擬機運行時數據區的一部分,也非《Java 虛擬機規範》定義的內存區域。但該部份內存被頻繁使用(例如 NIO),並且可能致使 OutOfMemoryError。
NIO(New input/output)是JDK1.4中新加入的類,引入了一種基於通道(channel)和緩衝區(buffer)的I/O方式,它可使用Native函數庫直接分配堆外內存,而後經過堆上的DirectByteBuffer對象對這塊內存進行引用和操做。
能夠看出,直接內存的大小並不受到java堆大小的限制,甚至不受到JVM進程內存大小的限制。它只受限於本機總內存(RAM及SWAP區或者分頁文件)大小以及處理器尋址空間的限制(最多見的就是32位/64位CPU的最大尋址空間限制不一樣)。
直接內存出現OutOfMemoryError的緣由是對該區域進行內存分配時,其內存與其餘內存加起來超過最大物理內存限制(包括物理的和操做系統級的限制),從而致使OutOfMemoryError。另外,若咱們經過參數「-XX:MaxDirectMemorySize」指定了直接內存的最大值,其超過指定的最大值時,也會拋出內存溢出異常。
GC
GC主要作了兩個工做,一個是內存的劃分和分配,一個是對垃圾進行回收。後續咱們會對這塊展開講解
垃圾標記算法
關於對垃圾進行回收,被引用的對象是存活的對象,而不被引用的對象是死亡的對象也就是垃圾,GC要區分出存活的對象和死亡的對象,也就是垃圾標記,並對垃圾進行回收。目前有兩種垃圾標記算法,分別是引用計數算法和根搜索算法,這兩個算法都和引用有些關聯。
引用:在JDK1.2以後,Java將引用分爲強引用、軟引用、弱引用和虛引用。
強引用:當咱們new一個對象時就是建立了一個具備強引用的對象,若是一個對象具備強引用,垃圾收集器就毫不會回收它。Java虛擬機寧願拋出OutOfMemoryError異常,使程序異常終止,也不會回收具備強引用的對象來解決內存不足的問題。
軟引用:若是一個對象只具備軟引用,當內存不夠時,會回收這些對象的內存,回收後若是仍是沒有足夠的內存,就會拋出OutOfMemoryError異常。Java提供了SoftReference類來實現軟引用。
弱引用:弱引用比起軟引用具備更短的生命週期,垃圾收集器一旦發現了只具備弱引用的對象,無論當前內存是否足夠,都會回收它的內存。Java提供了WeakReference類來實現弱引用。
虛引用:虛引用並不會決定對象的生命週期,若是一個對象僅持有虛引用,這就和沒有任何引用同樣,在任什麼時候候均可能被垃圾收集器回收。一個只具備虛引用的對象,被垃圾收集器回收時會收到一個系統通知,這也是虛引用的主要做用。Java提供了PhantomReference類來實現虛引用。
以上,瞭解jvm還須要你們瞭解如下算法:
引用計數算法
根搜索算法
標記-清除算法
複製算法
標記-壓縮算法
分代收集算法
若是想象深刻了解jvm內存管理建議仍是Google下以上算法內容
關於Dalvik和ART虛擬機本片文章只作對jvm的簡單對比。具體詳細內容請產考老羅具體對其分析:推薦:https://www.kancloud.cn/alex_wsc/androids/401771
Dalvik
Dalvik是Google專門爲Android操做系統開發的虛擬機。它支持.dex(即「Dalvik Executable」)格式的Java應用程序的運行。.dex格式是專爲Dalvik設計的一種壓縮格式,適合內存和處理器速度有限的系統。Dalvik由Dan Bornstein編寫,名字來源於他的祖先曾經居住過的小漁村達爾維克(Dalvík),位於冰島。
Dalvik虛擬機,簡稱DVM。DVM是Google專門爲Android平臺開發的虛擬機,它運行在Android運行時庫中。須要注意的是DVM並非一個Java虛擬機。
DVM和JVM的區別 DVM之因此不是一個JVM ,主要緣由是DVM並無遵循JVM規範來實現。DVM與JVM主要有如下區別。
基於的架構不一樣:JAVA虛擬機基於 棧結構,程序在運行時虛擬機須要頻繁的從棧上讀取寫入數據,這個過程須要更多的指令分派與內存訪問次數,會耗費不少CPU時間。Dalvik虛擬機基於 寄存器架構,數據的訪問經過寄存器間直接傳遞,這樣的訪問方式比基於棧方式要快不少。
基於棧的架構具備更好的可移植性,由於其實現不依賴於物理寄存器
基於棧的架構一般指令更短,由於其操做不須要指定操做數和結果的地址
基於寄存器的架構一般運行速度更快,由於有寄存器的支撐
基於寄存器的架構一般須要較少的指令來完成一樣的運算,由於不須要進行壓棧和出棧
執行的字節碼不一樣:Java運行的是Java字節碼,DVM運行的是Dalvik字節碼。Java類會被編譯成一個或多個.class文件,打包成jar文件,然後JVM會經過相應的.class文件和jar文件獲取相應的字節碼。而DVM會用dx工具將全部的.class文件轉換爲一個.dex文件,而後DVM會從該.dex文件讀取指令和數據。當JVM加載該.jar文件的時候,會加載裏面的全部的.class文件,JVM的這種加載方式很慢,對於內存有限的移動設備並不合適。而在.apk文件中只包含了一個.dex文件,這個.dex文件裏面將全部的.class裏面所包含的信息所有整合在一塊兒了,這樣再加載就提升了速度。.class文件存在不少的冗餘信息,dex工具會去除冗餘信息,並把全部的.class文件整合到.dex文件中,減小了I/O操做,提升了類的查找速度。
class文件格式:
Dalvik可執行文件體積更小(緣由同第二點)
DVM容許在有限的內存中同時運行多個進程:DVM通過優化,容許在有限的內存中同時運行多個進程。在Android中的每個應用都運行在一個DVM實例中,每個DVM實例都運行在一個獨立的進程空間。獨立的進程能夠防止在虛擬機崩潰的時候全部程序都被關閉。
ART虛擬機
ART(Android Runtime)是Android 4.4發佈的,用來替換Dalvik虛擬,Android 4.4默認採用的仍是DVM,系統會提供一個選項來開啓ART。在Android 5.0時,默認採用ART,DVM今後退出歷史舞臺。
DVM中的應用每次運行時,字節碼都須要經過即時編譯器(JIT,just in time)轉換爲機器碼,這會使得應用的運行效率下降。而在ART中,系統在安裝應用時會進行一次預編譯(AOT,ahead of time),將字節碼預先編譯成機器碼並存儲在本地,這樣應用每次運行時就不須要執行編譯了,運行效率也大大提高。
在不一樣平臺DEX轉化爲ODEX的過程,以下圖所示:
從安裝過程上來看 Java的代碼實際上須要兩次「轉換」才能夠在android設備上運行
一.PC端:.class->.dex->.apk
二.phone:dex->odex
ART : .dex->.odex(機器碼)(AOT Ahead-Of-Time) Dalvik: .dex->.odex(字節碼)(JIT Just-In-Time)
機器碼可直接執行,而字節碼每次啓動都須要執行將優化過的odex字節碼再轉換成機器碼
ART優勢:
應用運行更快,由於 DEX 字節碼的翻譯在應用安裝是就已經完成。
減小應用的啓動時間,由於直接執行的是 native 代碼。
提升設備的續航能力,由於節約了用於一行一行解釋字節碼所須要的電池。
支持更低的硬件
ART缺點:
因爲在安裝時時生成的 native 機器碼是存儲在內部存儲器上,因此須要更多的內部存儲空間。(大概多個10%~20%)
應用安裝須要更長的時間,由於 DEX 字節碼須要在安裝時就翻譯成機器碼。
本篇文章內容較長,主要時便於後期對android內存管理及優化打好基礎,感興趣的童鞋能夠收藏慢慢看。
夯實基礎,關注前沿,娛樂生活
掌握更多前沿技術,獲取更多笑點
請關注--------喘口仙氣
本文分享自微信公衆號 - 喘口仙氣(gh_db8538619cdd)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。