咱們都知道java程序必須在虛擬機上運行。那麼虛擬機究竟是什麼呢?先看網上搜索到的比較靠譜的解釋:java
虛擬機是一種抽象化的計算機,經過在實際的計算機上仿真模擬各類計算機功能來實現的。Java虛擬機有本身完善的硬體架構,如處理器、堆棧、寄存器等,還具備相應的指令系統。JVM屏蔽了與具體操做系統平臺相關的信息,使得Java程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就能夠在多種平臺上不加修改地運行。linux
這種解釋應該算是正確的,可是隻描述了虛擬機的外部行爲和功能,並無針對內部原理作出說明。通常狀況下咱們不須要知道虛擬機的運行原理,只要專一寫java代碼就能夠了,這也正是虛擬機之因此存在的緣由--屏蔽底層操做系統平臺的不一樣而且減小基於原生語言開發的複雜性,使java這門語言可以跨各類平臺(只要虛擬機廠商在特定平臺上實現了虛擬機),而且簡單易用。這些都是虛擬機的外部特性,可是從這些信息來解釋虛擬機,未免太籠統了,沒法讓咱們知道內部原理。程序員
讓咱們嘗試從操做系統的層面來理解虛擬機。咱們知道,虛擬機是運行在操做系統之中的,那麼什麼東西才能在操做系統中運行呢?固然是進程,由於進程是操做系統中的執行單位。能夠這樣理解,當它在運行的時候,它就是一個操做系統中的進程實例,當它沒有在運行時(做爲可執行文件存放於文件系統中),能夠把它叫作程序。編程
對命令行比較熟悉的同窗,都知道其實一個命令對應一個可執行的二進制文件,當敲下這個命令而且回車後,就會建立一個進程,加載對應的可執行文件到進程的地址空間中,而且執行其中的指令。下面對比C語言和Java語言的HelloWorld程序來講明問題。架構
首先編寫C語言版的HelloWorld程序。編程語言
#include #include int main(void) { printf("hello world\n"); return 0; }
編譯C語言版的HelloWorld程序:學習
gcc HelloWorld.c -o HelloWorld
運行C語言版的HelloWorld程序:spa
zhangjg@linux:/deve/workspace/HelloWorld/src$ ./HelloWorld hello world
gcc編譯器編譯後的文件直接就是可被操做系統識別的二進制可執行文件,當咱們在命令行中敲下 ./HelloWorld這條命令的時候, 直接建立一個進程, 而且將可執行文件加載到進程的地址空間中, 執行文件中的指令。操作系統
做爲對比, 咱們看一下Java版HelloWord程序的編譯和執行形式。命令行
首先編寫源文件HelloWord.java :
public class HelloWorld { public static void main(String[] args) { System.out.println("HelloWorld"); } }
編譯Java版的HelloWorld程序:
zhangjg@linux:/deve/workspace/HelloJava/src$ javac HelloWorld.java zhangjg@linux:/deve/workspace/HelloJava/src$ ls HelloWorld.class HelloWorld.java
運行Java版的HelloWorld程序:
zhangjg@linux:/deve/workspace/HelloJava/src$ java -classpath . HelloWorld HelloWorld
從上面的過程能夠看到, 咱們在運行Java版的HelloWorld程序的時候, 敲入的命令並非 ./HelloWorld.class 。 由於class文件並非能夠直接被操做系統識別的二進制可執行文件 。 咱們敲入的是java這個命令。 這個命令說明, 咱們首先啓動的是一個叫作java的程序, 這個java程序在運行起來以後就是一個JVM進程實例。
上面的命令執行流程是這樣的:
java命令首先啓動虛擬機進程,虛擬機進程成功啓動後,讀取參數「HelloWorld」,把他做爲初始類加載到內存,對這個類進行初始化和動態連接(關於類的初始化和動態連接會在後面的博客中介紹),而後從這個類的main方法開始執行。也就是說咱們的.class文件不是直接被系統加載後直接在cpu上執行的,而是被一個叫作虛擬機的進程託管的。首先必須虛擬機進程啓動就緒,而後由虛擬機中的類加載器加載必要的class文件,包括jdk中的基礎類(如String和Object等),而後由虛擬機進程解釋class字節碼指令,把這些字節碼指令翻譯成本機cpu可以識別的指令,才能在cpu上運行。
從這個層面上來看,在執行一個所謂的java程序的時候,真真正正在執行的是一個叫作Java虛擬機的進程,而不是咱們寫的一個個的class文件。這個叫作虛擬機的進程處理一些底層的操做,好比內存的分配和釋放等等。咱們編寫的class文件只是虛擬機進程執行時須要的「原料」。這些「原料」在運行時被加載到虛擬機中,被虛擬機解釋執行,以控制虛擬機實現咱們java代碼中所定義的一些相對高層的操做,好比建立一個文件等,能夠將class文件中的信息看作對虛擬機的控制信息,也就是一種虛擬指令。
編程語言也有本身的原理, 學習一門語言, 主要是把它的原理搞明白。 看似一個簡單的HelloWorld程序, 也有不少深刻的內容值得剖析。
爲了展現虛擬機進程和class文件的關係,特地畫了下面一張圖:
< 喎?http://www.Bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHAgY2xhc3M9"cjk"> 根據上圖表達的內容,咱們編譯以後的class文件是做爲Java虛擬機的原料被輸入到Java虛擬機的內部的,那麼具體由誰來作這一部分工做呢?其實在Java虛擬機內部,有一個叫作類加載器的子系統,這個子系統用來在運行時根據須要加載類。注意上面一句話中的「根據須要」四個字。在Java虛擬機執行過程當中,只有他須要一個類的時候,纔會調用類加載器來加載這個類,並不會在開始運行時加載全部的類。就像一我的,只有餓的時候纔去吃飯,而不是一次把一年的飯都吃到肚子裏。通常來講,虛擬機加載類的時機,在第一次使用一個新的類的時候。本專欄後面的文章會具體討論Java中的類加載器。
由虛擬機加載的類,被加載到Java虛擬機內存中以後,虛擬機會讀取並執行它裏面存在的字節碼指令。虛擬機中執行字節碼指令的部分叫作執行引擎。就像一我的,不是把飯吃下去就完事了,還要進行消化,執行引擎就至關於人的腸胃系統。在執行的過程當中還會把各個class文件動態的鏈接起來。關於執行引擎的具體行爲和動態連接相關的內容也會在本專欄後續的文章中進行討論。
咱們知道,Java虛擬機會進行自動內存管理。具體說來就是自動釋放沒有用的對象,而不須要程序員編寫代碼來釋放分配的內存。這部分工做由垃圾收集子系統負責。
從上面的論述能夠知道, 一個Java虛擬機實例在運行過程當中有三個子系統來保障它的正常運行,分別是類加載器子系統, 執行引擎子系統和垃圾收集子系統。 以下圖所示:
虛擬機的運行,必須加載class文件,而且執行class文件中的字節碼指令。它作這麼多事情,必須須要本身的空間。就像人吃下去的東西首先要放在胃中。虛擬機也須要空間來存放個中數據。首先,加載的字節碼,須要一個單獨的內存空間來存放;一個線程的執行,也須要內存空間來維護方法的調用關係,存放方法中的數據和中間計算結果;在執行的過程當中,沒法避免的要建立對象,建立的對象須要一個專門的內存空間來存放。關於虛擬機運行時數據區的內容,也會出如今本專欄後續的文章中。虛擬機的運行時內存區大概能夠分紅下圖所示的幾個部分。(這裏只是大概劃分, 並無劃分的很精細)
寫到這裏,基本上關於我對java虛擬機的理解就寫完了。這篇文章的主題雖然是深刻理解Java虛擬機,可是你可能感受一點也不「深刻」,也只是泛泛而談。我也有這樣的感受。限於本身水平有限,也只能這樣了,要是想深刻理解java虛擬機,強烈建議讀一下三本書:
《深刻Java虛擬機》
《深刻理解Java虛擬機JVM高級特性與最佳實踐》
《Java虛擬機規範》
其實我也讀過這幾本書,可是它們對虛擬機的解釋也是基於一個外部模型,而沒有深刻剖析虛擬機內部的實現原理。虛擬機是一個大而複雜的東西,實現虛擬機的人都是大牛級別的,若是不是參與過虛擬機的實現,應該不多有人能把它參透。本專欄後面的一些文章也參考了這三本書, 雖然講解Java語法的書不可勝數, 可是深刻講解虛擬機的書, 目前爲止我就見過這三本,而且網上的資料也不是不少。
最後作一個總結:
1 虛擬機並不神祕,在操做系統的角度看來,它只是一個普通進程。
2 這個叫作虛擬機的進程比較特殊,它可以加載咱們編寫的class文件。若是把JVM比做一我的,那麼class文件就是咱們吃的食物。
3 加載class文件的是一個叫作類加載器的子系統。就比如咱們的嘴巴,把食物吃到肚子裏。
4 虛擬機中的執行引擎用來執行class文件中的字節碼指令。就比如咱們的腸胃,對吃進去的食物進行消化。
5 虛擬機在執行過程當中,要分配內存建立對象。當這些對象過期無用了,必需要自動清理這些無用的對象。清理對象回收內存的任務由垃圾收集器負責。就比如人吃進去的食物,在消化以後,必須把廢物排出體外,騰出空間能夠在下次餓的時候吃飯並消化食物。