Java虛擬機的啓動是經過引導類加載器(Bootstrap Class Loader)
建立一個初始類(Initial Class)來完成的,這個是由虛擬機的具體實現指定的。html
Java虛擬機進程
。咱們在執行一個簡單的程序Main方法程序的時候,實際上會加載很是多的類。當咱們在代碼中加入掛起代碼,而後在掛起期間採用JPS指令查看JVM當前所在運行的進程號(pid)以下: Main類獨立地佔用了一個進程號,而其餘的輔助運行類也在運行;當程序執行完成後,天然而然地Main方法消失了
java
在HotSpot虛擬機中,每一個線程都與操做系統的本地線程直接映射。當一個Java線程準備好執行之後,此時一個操做系統的本地線程也同時建立,Java線程執行終止後,本地線程也會回收。操做系統負責全部線程的安排調度到任何一個可用的CPU上,一旦本地線程初始化成功以後,他就會調用Java線程中的run()方法。線程又區分爲守護線程
和非守護線程
,若是程序中剩下的只有守護線程,那麼虛擬機也會退出。數據庫
一個Java程序背後其實有不少的線程:安全
虛擬機線程
:這種線程的操做時須要JVM到達安全點纔會出現,這些操做必須在不一樣的線程中發生的緣由是:他們都須要JVM達到安全點
,這樣堆纔不會變化。這種線程的執行的任務包括「STOP-THE-WORLD」,即讓全部線程都終止的垃圾收集、線程棧收集、線程掛起、偏向鎖撤銷。週期任務線程
:這種線程是時間週期的體現,好比中斷等等,他們通常用於週期性操做的調度執行。GC線程
:這種線程對在JVM裏不一樣種類的垃圾收集行爲提供了支持。編譯線程
:這種線程在運行時會將字節碼編譯成本地代碼。信號調度線程
:這種線程接受信號發送給JVM,在它內部經過調用適當的的方法進行處理。
- JVM安全點:在虛擬機在進行
可達性分析
時,HotSpot虛擬機會在特定的位置記錄在哪有引用,這些特定的位置就叫作安全點。這是GC方面的知識,以後會作解析。
咱們知道,JVM讀入Class文件進行加載。通常的讀入方式都是存在於本地的磁盤中,可是實際上,類加載支持:本地、網絡、JAR包、甚至是運行時計算生成獲得的Class文件。markdown
運輸工具(Class Loader)
,扮演一個快遞員的操做。而這個運輸工具
就是咱們今天的主角:類加載子系統。加載階段主要的任務就是,讀入Class文件,構建靜態存儲結構,在內存中生成元數據模板。詳細過程以下:網絡
加載的幾個來源:數據結構
連接階段主要是驗證Class文件的有效性、準確性和爲類中的信息初始化變量值、執行靜態方法。連接階段又劃分爲三個小的階段:多線程
驗證的目的在於確保Class文件的字節流中包含信息符合當前的虛擬機要求,保證被加載類的正確性,保證虛擬機自身的安全。包括:函數
CA FE BA BE
(Cafe babe)。何爲元數據?【百度百科】工具
元數據(Metadata)
,爲描述數據的數據
,主要是描述數據屬性的信息,用來支持如指示存儲位置、歷史數據、資源查找、文件記錄等功能。- 元數據最大的好處是,它使信息的描述和分類能夠實現格式化,從而爲機器處理創造了可能。
其餘的內容能夠看看這篇博客:元數據(MetaData)
爲類變量
分配內存而且設置該類變量的默認初始值。即零值。有以下代碼:
class Test{
public static int a = 1;
}
複製代碼
在準備階段,此時的類變量a
的值僅僅是0,而不是1。
- 這裏不包括被final修飾的static變量,由於Final在編譯階段就會分配一個固定的值,編譯期即把結果放入了常量池。在運行時被初始化,能夠直接將這個固定死的值賦值給它。賦值後不可修改,可是常量池中只能引用到基本數據類型+String。
- 這裏也不會爲實例變量分配初始化。由於類變量會分配在方法區中,而實例變量則是對象,會和其餘對象同樣分配到Java區中。(這裏不是說類變量不是對象,而是說類變量自己是一種特殊的對象。他被分配在方法區中,而不是和其餘new出來的對象同樣分配在堆中。)
其實,準備階段
的操做能夠簡單地理解爲:只構建一個最爲簡單的類,除非咱們定下了寫死的final staitc且是(8+1)的基本數據類型之外,都是賦默認零值,引用類型爲null。
指針
、相對偏移量
或一個間接定位到目標的句柄
。做爲第二個大的階段連接階段
,僅僅是完成了類的載入和數據類型的初始化,除了部分常量值,其餘的類對象、實例對象都沒有被正確賦值。而第三個階段初始化階段
則是完成遺留下的問題的。
<Clinit>()方法
,注意,構造器方法和構造方法不一樣。<Clinit>()方法
不一樣於類的構造器:構造器是虛擬機視角下的<init>()
。<Clinit>()方法
執行前,父類的<Clinit>()方法
語句執行完成。<Clinit>()方法
在多線程的狀況下被同步加鎖
,一個類的靜態代碼塊的初始化只能執行一次。這也是咱們靜態內部類實現線程安全的懶漢式單例的重要理論基礎。
單例模式
分爲餓漢式和懶漢式,餓漢式天生線程安全,可是作不到懶加載。而懶漢式可以實現線程安全,可是須要加鎖處理,不然多線程可能會建立不一樣的實例。目前的幾種實現線程安全的懶漢式單例方式有常見的加鎖處理。而靜態內部類的特性可使得它自然地實現懶漢式的單例模式。 首先是線程安全,在上文的3.5中,咱們知道,一個類的靜態代碼塊的初始化會被JVM加鎖,這樣一來,咱們就不須要手動加鎖了。 其次是懶加載,只有咱們調用到的時候,類加載器纔會爲咱們加載靜態內部類,不然是不會加載的,這樣一來,咱們就實現了線程安全的懶漢式單例模式。
2.構造器、構造函數、
<init> 實例構造器
和<Clinit>()類構造器
構造函數
:也叫構造方法,就是咱們寫代碼裏面new一個類的構造方法。構造器
:Javac編譯,生成的一個函數,是在字節碼層面存在的「函數」。它其實對一些代碼的整合後生成的函數。<init> 實例構造器
:針對的是實例構造。- ·
<Clinit>()類構造器
:cinit針對是類。數量上來來說
<init> 實例構造器
至少存在一個,<Clinit>()類構造器
構造器只存在一個. 由於類對象在JVM內存中只會存在一個(同一個類加載器)。 3. 枚舉類。枚舉類是一種比較特殊的類,它的底層實質上仍是Class,只不過是成員變量被public static final修飾的成員變量(經過類名調用),因此它是在static靜態代碼塊中一塊兒初始化的。因爲java類的加載和初始化過程都是線程安全的,因此建立一個enum類型是線程安全的,因此用枚舉類實現一個線程安全的單例是可行的。
####代碼1:
pivate static int number = 1;
複製代碼
序號 | 階段 | 值 |
---|---|---|
1 | 加載階段 | - |
2 | 連接-驗證 | - |
3 | 連接-準備 | 0 |
4 | 連接-解析 | 0 |
5 | 初始化階段 | 10 |
private final static int number = 10;
複製代碼
序號 | 階段 | 值 |
---|---|---|
1 | 加載階段 | - |
2 | 連接-驗證 | - |
3 | 連接-準備 | 10 |
4 | 連接-解析 | 10 |
5 | 初始化階段 | 10 |
注意,這裏的各類虛擬機的實現各有不一樣,例如HotSpot虛擬機在
驗證-準備階段
就已經賦初值了,可是JVM規範是要在初始化階段
才賦初值的。詳細可見參考來源1
private int number = 10;
複製代碼
在具體的實例建立的時候纔會賦初值。
##5 類加載器 前面,咱們介紹了一個類被加載進入JVM的不一樣的階段,然而具體執行加載過程的是咱們的類加載器(Class Loader)
。
類加載器劃分紅三種:
BootStrap ClassLoader
:這是由C/C++編寫的類架子啊器,嵌套在JVM內部。用來加載Java的核心類庫,用於加載:JAVA_HOME/jre/lib/rt.jar、resources.jar或者是sun.boot.class.path目錄下的文件
。
BootStrapClassLoader
沒有父加載器,可是他是擴展類加載器的父加載器。BootStrapClassLoader
只能加載包名爲java、javax、sun
開頭的類。
Extension Classloader
i. 擴展類加載器:由Java語言編寫,派生於BootStrapClassLoader
。從java.ext.dirs
系統屬性所制定的目錄中加載類庫,或從JDK的安裝目錄的jre/lib/ext子目錄(或者擴展目錄)
下加載類庫。若是用戶建立的JAR放在此目錄下,也會由擴展類加載器進行加載。
ii. 應用程序類加載器(系統類加載器):由Java語言編寫,派生於BootStrapClassLoader
,負責加載環境變量classpath或者系統屬性java.class.path指定路徑
下的類庫。該類是默認的類加載器。通常來講,Java應用的類都是由它來完成加載。
iii. 用戶自定義的類加載器。
網路上關於雙親委派機制的講解不少,通俗地說,受權委派機制就是當類加載的時候,咱們將任務"承包"給了類加載器,然而類加載器並不會本身去加載類,而是優先提交給其父類去加載,若是父類能加載,那麼我本身就不加載,使用父類加載獲得的類數據。
這樣作的目的是爲了保護類加載的來源,防止類的重複加載和核心API被惡意篡改致使的代碼泄漏或者是功能缺失。
JVM必須知道一個類型是由BootStrap ClassLoader
仍是其餘的ClassLoader加載的。
若是一個類型是用戶類加載器加載的,那麼JVM會將這個類加載器的一個引用做爲類型信息的一部分保存在方法區中,當解析一個類型到另外一個類型的引用時,JVM須要保證兩個類型的類加載器是相同的。
JVM對類的使用分紅主動使用和被動使用。其中主動使用又細分爲七種狀況:
建立類的實例
訪問某個類或者接口的靜態變量,或者對該靜態變量賦值。
調用類的靜態方法
反射
初始化一個類的子類
Java虛擬機啓動時被標明爲啓動類的類
JDK7開始提供的動態語言支持Java.lang.invoke.MethodHandle
實例的解析結果REF_getStatic
、REF_putStatic
、REF_invokeStatic
句柄對應的類沒有初始化則初始化。
除了以上的七種狀況,其餘的調用都被視做是對類的被動調用
,都不會致使類的初始化。
若是同一個類被不一樣的類加載器所加載,那麼這兩個類是不一樣的類。
.Class文件會被類加載子系統讀入JVM中的方法區當中,在方法區中,存放了最基本的類信息,而類加載子系統運行的各個階段會爲咱們類賦值、調用靜態方法等等。如圖中的紅線,就是類加載的主要過程。