JVM
是Java Virtual Machine
(Java虛擬機)的縮寫,JVM
是一種用於計算設備的規範,它是一個虛構的計算機,是經過在實際的計算機上仿真模擬各類計算機功能來實現的。java
JVM屏蔽了與具體操做系統平臺相關的信息,使Java
程序只需生成在Java
虛擬機上一次編譯,屢次運行,具備跨平臺性。JVM
在執行字節碼時,實際上最終仍是把字節碼解釋成具體平臺上的機器指令執行。編程
Java
虛擬機包括一套字節碼指令集、一組寄存器、一個棧、一個垃圾回收堆和一個存儲方法區。後端
本文將簡述如下內容:數組
JVM
,JRE
,JDK
都是 java
語言的支柱,他們分工協做。但不一樣的是 Jdk
和 JRE
是真實存在的,而 JVM
是一個抽象的概念,並不真實存在。緩存
JDK
(Java Development Kit) 是 Java
語言的軟件開發工具包(SDK
)。JDK
物理存在,是 programming tools
、JRE
和 JVM
的一個集合。安全
JRE
(Java Runtime Environment)Java
運行時環境,JRE
是物理存在的,主要由Java API
和 JVM
組成,提供了用於執行 java
應用程序最低要求的環境。網絡
JVM
是一種用於計算設備的規範,它是一個虛構的計算機的軟件實現,簡單的說,JVM
是運行byte code
字節碼程序的一個容器。多線程
基於堆棧的虛擬機 :最流行的計算機體系結構,如英特爾X86
架構和ARM
架構上運行基於寄存器 。好比,安卓的Davilk
虛擬機就是基於寄存器結構 可是,JVM
是基於棧結構的。架構
符號引用 :除了基本類型之外的數據 (類和接口) 都是經過符號來引用,而不是經過顯式地使用內存地址來引用。框架
垃圾收集 :一個類的實例是由用戶程序建立和垃圾回收自動銷燬。
網絡字節順序 :Java class
文件用網絡字節碼順序來進行存儲,保證了小端的Intel x86
架構和大端的RISC
系列的架構之間的無關性。
JVM
使用Java字節碼的方式,做爲Java
用戶語言 和 機器語言 之間的中間語言。實現一個通用的、 機器無關 的執行平臺。
基於安全方面考慮,JVM
要求在 class
文件中使用強制性的語法和約束,但任意一門語言均可以轉換爲被 JVM
接受的有效的 class
文件。做爲一個通用的、機器無關的執行平臺,任何其餘語言的實現者均可將 JVM
看成他的語言產品交付媒介。
JVM
中執行過程以下:
JVM
生命週期main
方法的class
均可以做爲JVM
實例運行的起點。運行:main
函數爲起點,程序中的其餘線程均有它啓動,包括daemon
守護線程和non-daemon
普通線程。daemon
是JVM
本身使用的線程好比GC
線程,main
方法的初始線程是non-daemon
。
消亡:全部線程終止時,JVM
實例結束生命。
JVM
組成架構JAVA
代碼執行過程以下:
類加載器 負責加載程序中的類型(類和接口),並賦予惟一的名字予以標識。
Bootstrap Classloader
是在Java
虛擬機啓動後初始化的。
Bootstrap Classloader
負責加載 ExtClassLoader
,而且將 ExtClassLoader
的父加載器設置爲 Bootstrap Classloader
Bootstrap Classloader
加載完 ExtClassLoader
後,就會加載 AppClassLoader
,而且將 AppClassLoader
的父加載器指定爲 ExtClassLoader
。
Class Loader | 實現 | 負責加載 |
---|---|---|
Bootstrap Loader | C++ | %JAVA_HOME%/jre/lib , %JAVA_HOME%/jre/classes 以及-Xbootclasspath參數指定的路徑以及中的類 |
Extension ClassLoader | Java | %JAVA_HOME%/jre/lib/ext ,路徑下的全部classes 目錄以及java.ext.dirs 系統變量指定的路徑中類庫 |
Application ClassLoader | Java | Classpath 所指定的位置的類或者是jar 文檔,它也是Java 程序默認的類加載器 |
Java
中ClassLoader
的加載採用了雙親委託機制,採用雙親委託機制加載類的時候採用以下的幾個步驟:
當前ClassLoader
首先從本身已經加載的類中查詢是否此類已經加載,若是已經加載則直接返回原來已經加載的類。
當前ClassLoader
的緩存中沒有找到被加載的類的時候,委託父類加載器去加載,父類加載器採用一樣的策略,首先查看本身的緩存,而後委託父類的父類去加載,一直到Bootstrap ClassLoader
。
當全部的父類加載器都沒有加載的時候,再由當前的類加載器加載,並將其放入它本身的緩存中,以便下次有加載請求的時候直接返回。
小結 :雙親委託機制的核心思想分爲兩個步驟。其一,自底向上檢查類是否已經加載;其二,自頂向下嘗試加載類。
ClassLoader
隔離問題每一個類裝載器都有一個本身的命名空間用來保存已裝載的類。當一個類裝載器裝載一個類時,它會經過保存在命名空間裏的類全侷限定名(Fully Qualified Class Name
)進行搜索來檢測這個類是否已經被加載了。
JVM
及 Dalvik
對類惟一的識別是 ClassLoader id
+ PackageName
+ ClassName
,因此一個運行程序中是有可能存在兩個包名和類名徹底一致的類的。而且若是這兩個」類」不是由一個 ClassLoader
加載,是沒法將一個類的示例強轉爲另一個類的,這就是 ClassLoader
隔離。
雙親委託 是 ClassLoader
類一致問題的一種解決方案,也是 Android
差價化開發和熱修復的基礎。
Java
提供了動態加載特性。在運行時的第一次引用到一個class
的時候會對它進行裝載(Loading) 、 連接(Linking) 和 初始化(Initialization) ,而不是在編譯時進行。不一樣的JVM的實現不一樣,本文所描述的內容均只限於Hotspot JVM
。
JVM
的類裝載器負責動態裝載,Java
的類裝載器有以下幾個特色:
層級結構:Java裏的類裝載器被組織成了有父子關係的層級結構。Bootstrap類裝載器是全部裝載器的父親。
代理模式: 基於層級結構,類的代理能夠在裝載器之間進行代理。當裝載器裝載一個類時,首先會檢查它在父裝載器中是否進行了裝載。若是上層裝載器已經裝載了這個類,這個類會被直接使用。反之,類裝載器會請求裝載這個類
可見性限制:一個子裝載器能夠查找父裝載器中的類,可是一個父裝載器不能查找子裝載器裏的類。
不容許卸載:類裝載器能夠裝載一個類可是不能夠卸載它,不過能夠刪除當前的類裝載器,而後建立一個新的類裝載器裝載。
加載(Loading)
首先,根據類的全限定名找到表明這個類的Class
文件,而後讀取到一個字節數組中。接着,這些字節會被解析檢驗它們是否表明一個Class
對象 幷包含正確的major
、minor
版本信息。直接父類 的類和接口也會被加載進來。這些操做一旦完成,類或者接口對象 就從二進制表示中建立出來了。
連接(Linking)
連接是檢驗類或接口並準備類型和父類接口的過程。連接過程包含三步:校驗(Verifying)、準備(Preparing)、部分解析(Optionally resolving)。
驗證
這是類裝載中最複雜的過程,而且花費的時間也是最長的。任務是確保導入類型的準確性,驗證階段作的檢查,運行時不須要再作。雖然減慢加了載速度,可是避免了屢次檢查。
準備
準備過程一般分配一個結構用來存儲類信息,這個結構中包含了類中定義的成員變量,方法 和接口信息等。
解析
解析是可選階段,把這個類的常量池中的全部的符號引用改變成直接引用。若是不執行,符號解析要等到字節碼指令使用這個引用時纔會進行。
初始化(Initialization)
把類中的變量初始化成合適的值。執行靜態初始化程序,把靜態變量初始化成指定的值。
JVM
規範定義了上面的幾個任務,不過它容許具體執行的時候可以有些靈活的變更。
經過類裝載器裝載的,被分配到JVM
的運行時數據區的字節碼會被執行引擎執行。
執行引擎 以指令爲單位讀取 Java
字節碼。它就像一個 CPU
同樣,一條一條地執行機器指令。每一個字節碼指令都由一個1字節的操做碼和附加的操做數組成。執行引擎 取得一個操做碼,而後根據操做數來執行任務,完成後就繼續執行下一條操做碼。
不過 Java
字節碼是用一種人類能夠讀懂的語言編寫的,而不是用機器能夠直接執行的語言。所以,執行引擎 必須把字節碼轉換成能夠直接被 JVM
執行的語言。
字節碼 能夠經過如下兩種方式轉換成機器語言:
解釋器
解釋器 一條一條地讀取字節碼,解釋 而且 執行 字節碼指令。由於它一條一條地解釋和執行指令,因此它能夠很快地解釋字節碼,可是執行起來會比較慢。這是解釋執行的語言的一個缺點。字節碼這種「語言」基原本說是解釋執行的。
即時(Just-In-Time)編譯器
即時編譯器 被引入用來彌補解釋器的缺點。執行引擎 首先按照 解釋執行 的方式來執行,而後在合適的時候,即時編譯器 把 整段字節碼 編譯成 本地代碼。而後,執行引擎就沒有必要再去解釋執行方法了,它能夠直接經過本地代碼去執行它。執行本地代碼比一條一條進行解釋執行的速度快不少。編譯後的代碼能夠執行的很快,由於本地代碼是保存在緩存裏的。
Java
字節碼是解釋執行的,可是沒有直接在 JVM
宿主執行原生代碼快。爲了提升性能,Oracle Hotspot
虛擬機會找到執行最頻繁的字節碼片斷並把它們編譯成原生機器碼。編譯出的原生機器碼被存儲在非堆內存的代碼緩存中。
經過這種方法(JIT)
,Hotspot
虛擬機將權衡下面兩種時間消耗:將字節碼編譯成本地代碼須要的額外時間和解釋執行字節碼消耗更多的時間。
這裏插入一下 Android 5.0 之後用的 ART 虛擬機使用的是 AOT 機制。
Dalvik
是依靠一個Just-In-Time (JIT)
編譯器去解釋字節碼。開發者編譯後的應用代碼須要經過一個解釋器在用戶的設備上運行,這一機制並不高效,但讓應用能更容易在不一樣硬件和架構上運行。ART
則徹底改變了這套作法,在應用安裝時就預編譯字節碼到機器語言,這一機制叫Ahead-Of-Time (AOT)
編譯。在移除解釋代碼這一過程後,應用程序執行將更有效率,啓動更快。
周志明,深刻理解Java虛擬機:JVM高級特性與最佳實踐,機械工業出版社
歡迎關注技術公衆號: 零壹技術棧
本賬號將持續分享後端技術乾貨,包括虛擬機基礎,多線程編程,高性能框架,異步、緩存和消息中間件,分佈式和微服務,架構學習和進階等學習資料和文章。