JVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用於計算設備的規範,它是一個虛構出來的計算機,是經過在實際的計算機上仿真模擬各類計算機功能來實現的。Java虛擬機包括一套字節碼指令集、一組寄存器、一個棧、一個垃圾回收堆和一個存儲方法域。 JVM屏蔽了與具體操做系統平臺相關的信息,使Java程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就能夠在多種平臺上不加修改地運行。JVM在執行字節碼時,實際上最終仍是把字節碼解釋成具體平臺上的機器指令執行。c++
Java語言的一個很是重要的特色就是與平臺的無關性。而使用Java虛擬機是實現這一特色的關鍵。通常的高級語言若是要在不一樣的平臺上運行,至少須要編譯成不一樣的目標代碼。而引入Java語言虛擬機後,Java語言在不一樣平臺上運行時不須要從新編譯。Java語言使用Java虛擬機屏蔽了與具體平臺相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就能夠在多種平臺上不加修改地運行。Java虛擬機在執行字節碼時,把字節碼解釋成具體平臺上的機器指令執行。這就是Java的可以「一次編譯,處處運行」的緣由。算法
類加載子系統(Class Loader)segmentfault
類加載器分爲:自定義類加載器 < 系統類加載器 < 擴展類加載器 < 引導類加載器api
類加載過程分爲:加載、連接、驗證、初始化。數組
程序計數器(Program Counter Register)安全
是一塊較小的內存空間,能夠看做是當前線程所執行字節碼的行號指示器,指向下一個將要執行的指令代碼,由執行引擎來讀取下一條指令。網絡
虛擬機棧 (Stack Area)數據結構
棧是線程私有,棧幀是棧的元素。每一個方法在執行時都會建立一個棧幀。棧幀中存儲了局部變量表、操做數棧、動態鏈接和方法出口等信息。每一個方法從調用到運行結束的過程,就對應着一個棧幀在棧中壓棧到出棧的過程。多線程
本地方法棧 (Native Method Area)
JVM 中的棧包括 Java 虛擬機棧和本地方法棧,二者的區別就是,Java 虛擬機棧爲 JVM 執行 Java 方法服務,本地方法棧則爲 JVM 使用到的 Native 方法服務。
堆 (Heap Area)
堆是Java虛擬機所管理的內存中最大的一塊存儲區域。堆內存被全部線程共享。主要存放使用new關鍵字建立的對象。全部對象實例以及數組都要在堆上分配。垃圾收集器就是根據GC算法,收集堆上對象所佔用的內存空間。
Java堆分爲年輕代(Young Generation)和老年代(Old Generation);年輕代又分爲伊甸園(Eden)和倖存區(Survivor區);倖存區又分爲From Survivor空間和 To Survivor空間。
方法區(Method Area)
方法區同 Java 堆同樣是被全部線程共享的區間,用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼。更具體的說,靜態變量+常量+類信息(版本、方法、字段等)+運行時常量池存在方法區中。常量池是方法區的一部分。
JDK 8 使用元空間 MetaSpace 代替方法區,元空間並不在JVM中,而是在本地內存中
類加載器子系統負責從文件系統或者網絡中在家Class文件,class文件在文件開頭又特定的文件標識。
ClassLoader只負責class文件的加載,至於它是否能夠運行,則由ExecutionEngine
決定。
加載類的信息存放於一塊被稱爲方法區的內存空間。除了類的信息外,方法區中還會存放運行時常量池信息,可能還包括字符串字面量和數字常量(這部分常量信息是Class文件中常量池部分的內存映射)
類的加載過程大體分爲三個階段:加載,連接,初始化。
java.lang.Class
對象,做爲方法區這個類的各類數據的訪問入口驗證(Verify)
準備(Prepare)
準備階段是進行內存分配。爲類變量也就是類中由static修飾的變量分配內存,而且設置初始值,這裏要注意,初始值是默認初始值0、null、0.0、false等
,而不是代碼中設置的具體值,代碼中設置的值是在初始化階段
完成的。另外這裏也不包含用final修飾的靜態變量,由於final在編譯的時候就會分配了。這裏不會爲實例變量分配初始化,類變量會分配在方法區中,而實例對象會隨着對象一塊兒分配到Java堆中。
public class HelloApp { private static int a = 1; // 準備階段爲0,而不是1 public static void main(String[] args) { System.out.println(a); } }
解析(Resolve)
解析主要是解析字段、接口、方法。主要是將常量池中的符號引用替換爲直接引用的過程。直接引用就是直接指向目標的指針、相對偏移量等。
<clinit>()
的過程<clinit>()
不一樣於類的構造器(構造器是虛擬機視角下的<init>()
)<clinit>()
執行前,父類的<clinit>()
已經執行完畢<clinit>()
方法在多線程下被同步加鎖須要注意,若是沒有定義靜態變量或靜態代碼塊的話則沒有<clinit>()
案例以下:
public class HelloApp { static { code = 20; } private static int code = 10; //第一步:在準備階段初始化了code默認值爲0。 //第二步:根據類執行順序先執行靜態代碼塊,賦值爲20. //第三步:最後賦值爲10,輸出結果爲10. public static void main(String[] args) { System.out.println(code); // 10 } }
經過字節碼文件能夠很清楚的看到結果:
0 bipush 20 2 putstatic #3 <com/jvm/HelloApp.code> 5 bipush 10 7 putstatic #3 <com/jvm/HelloApp.code> 10 return
先被賦值爲20,而後改成10。
JVM支持兩種類型的類加載器,分別爲引導類加載器(Bootstrap ClassLoader)
和 自定義類加載器(User-Defined ClassLoader)
從概念上講,自定義類加載器通常指的是程序中由開發人員自定義的一類類加載器,可是Java虛擬機是將全部派生於抽象類ClassLoader的類加載器都劃分爲自定義類加載器
不管怎麼劃分,在程序中最多見的類加載器始終只有三個:
系統類加載器(System Class Loader) < 擴展類加載器(Extension Class Loader) < 引導類加載器(Bootstrap Class Loader)
它們之間的關係不是繼承關係,而是level關係。
系統類加載器和擴展類加載器間接或直接繼承ClassLoader。劃線分爲兩大類。
public class HelloApp { public static void main(String[] args) { //獲取系統類加載器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 //獲取其上層:擴展類加載器 ClassLoader extClassLoader = systemClassLoader.getParent(); System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@60e53b93 //獲取其上層:獲取不到引導類加載器 ClassLoader bootStrapLoader = extClassLoader.getParent(); System.out.println(bootStrapLoader);//null //咱們本身定義的類是由什麼類加載器加載的:使用系統類加載器 ClassLoader classLoader = HelloApp.class.getClassLoader(); System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 //看看String是由什麼類加載器加載的:使用引導類加載器 ClassLoader classLoaderString = String.class.getClassLoader(); System.out.println(classLoaderString);//null } }
引導類加載器(Bootstrap ClassLoader)
擴展類加載器(Extension ClassLoader)
若是用戶建立的Jar放在此目錄下,也會自動由擴展類加載器加載。
系統類加載器(System ClassLoader)
該類加載是程序中默認的類加載器
,通常來講,Java應用的類都有由它來完成加載。爲何須要用戶自定義類加載器?
用戶自定義類加載器實現步驟
關於ClassLoader
ClassLoader是一個抽象類,系統類加載器和擴展類加載器間接或直接繼承ClassLoader。
經常使用方法以下:
方法名稱 | 描述 |
---|---|
getParent() | 返回該類加載器的上一級類加載器 |
loadClass(String name) | 加載名稱爲name的類,返回結果爲java.lang.Class的實例 |
findClass(String name) | 查找名稱爲name的類,返回結果爲java.lang.Class的實例 |
findLoadedClass(String name) | 查找名稱爲name的已經被加載過的類,返回結果爲java.lang.Class類的實例 |
defineClass(String name,byte[] b,int off,int len) | 把字節數組b中的內容轉換爲一個java類,返回結果爲java.lang.Class類的實例 |
resolveClass(Class<?> c) | 鏈接指定的一個java類 |
Java虛擬機對class文件採用的是按需加載
的方式,也就是說須要使用該類的時候纔會將它的class文件加載到內存生成class對象。
當某個類加載器須要加載某個.class文件時,它首先把這個任務委託給他的上級類加載器,遞歸這個操做,若是上級的類加載器沒有加載,本身才會去加載這個類。
工做原理
好比咱們如今在本身項目中建立一個包名java.lang下建立一個String類。
package java.lang; public class String { static { System.out.println("我是本身建立的String"); } }
public class HelloApp { public static void main(String[] args) { String s = new String(); } }
執行以後並不會輸出咱們的語句,由於咱們的String類加載器一開始由系統類加載器一級一級往上委託,最終交給引導類加載器,引導類加載器一看是java.lang包下的,ok,我來執行,最終執行的並非咱們本身建立String類,保證了核心api沒法被纂改,避免類的重複加載。
package java.lang; public class String { static { System.out.println("我是本身建立的String"); } public static void main(String[] args) { System.out.println("Hello World !!!"); } }
若是咱們想運行如上代碼,咱們會獲得以下錯誤:
錯誤: 在類 java.lang.String 中找不到 main 方法, 請將 main 方法定義爲: public static void main(String[] args) 不然 JavaFX 應用程序類必須擴展javafx.application.Application
由於咱們知道在覈心api String類中是沒有main方法的,因此咱們能夠肯定加載的並非咱們本身建立的String類。
在JVM中表示兩個Class對象是否爲同一個類存在的必要條件:
順便說一句,咱們包名若是爲java.lang則會報錯。