Java代碼經歷三個階段:源代碼階段(Source) -> 類加載階段(ClassLoader) -> 運行時階段(Runtime)
首先我們來理清一下Java代碼整個執行過程, 讓我們對其有個整體的認識:
Java源程序(.java)經過Java編譯器(javac)以后, 生成一個或多個字節碼(.class)文件, JVM將每一條要執行的字節碼通過類加載器ClassLoader加載進內存, 再通過字節碼校驗器的校驗, Java解釋器翻譯成對應的機器碼, 最后在操作系統解釋運行.
當程序要使用某個類時, 如果該類還未被加載到內存中, 則系統會通過加載, 連接, 初始化三步來實現對這個類進行初始化:
加載就是將class文件讀入內存, 並為之創建一個Class對象(任何類被使用時系統都會創建且只創建一個Class對象)
JVM進行類加載階段需要完成以下三件事情:
1. 通過一個類的全限定名稱來獲取定義此類的二進制字節流
2. 將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構
3. 在java堆中生成一個代表這個類的java.lang.Class對象, 作為方法區這些數據的訪問入口
類的加載的最終產品是位於堆區中的Class對象, Class對象封裝了類在方法區內的數據結構, 並且向Java程序員提供了訪問方法區內的數據結構的接口
類的加載時機
1. 創建類的實例
2. 使用類的靜態變量或者為靜態變量賦值
3. 調用類的靜態方法
4. 使用反射方式來強制創建某個類或接口對應的java.lang.Class對象
5. 初始化某個類的子類
6. 直接使用java命令來運行某個主類
通俗的說就是只要用到了類的東西類就會加載
JVM在運行時會產生3個類加載器組成的初始化加載器層次結構
- Bootstrap ClassLoader 根類加載器
用C++編寫
也被稱為引導類加載器, 負責java核心類的加載 該加載器無法直接獲取
比如System, String等, 在JDK中JRE的lib目錄下rt,jar文件中
- Extension ClassLoader 擴展類加載器
負責JRE的擴展目錄中jar包的加載 jre/lib/ext目錄下的jar包或-Djava,ext,dirs指定目錄下的jar包裝入工作庫
- System ClassLoader 系統類加載器(加載自己寫的類以及第三方類庫(導入的jar包))
負責在JVM啟動時加載來自java命令的class文件, 以及classpath環境變量所指定的jar包和類路徑
連接就是將類的二進制數據合並到JRE中
連接分為以下三步:
驗證 檢查載入Class文件數據的正確性
-
-
- 文件格式檢驗:檢驗字節流是否符合Class文件格式的規范, 並且能被當前版本的虛擬機處理
- 元數據檢驗:對字節碼描述的信息進行語義分析, 以保證其描述的內容符合Java語言規范的要求
- 字節碼檢驗:通過數據流和控制流分析, 確定程序語義是合法、符合邏輯的
- 符號引用檢驗:符號引用檢驗可以看作是對類自身以外(常量池中的各種符號引用)的信息進行匹配性校驗
-
是否有正確的內部結構(構造器, 方法, 變量, 代碼塊), 並和其他類協調一致
准備 該階段正式為類變量分配內存並設置類變量初始值
這些變量所使用的內存將在方法區中進行分配, 此時進行內存分配的僅包括類變量, 而不包括實例變量(實例變量將會在對象實例化時隨着對象一起分配在Java堆中),
另外, 在這里分配的靜態類變量是將其值定義為默認值, 這里所設置的初始值通常情況下是數據類型默認的零值(如0, 0L, null, false等), 而不是被在Java代碼中
被顯式地賦予的值, 正確的賦值將在初始化階段執行,
解析 將類的二進制數據中的符號引用替換為直接引用
比如說類中方法中的運算, 運算中符號a=1 去掉a直接變成1, 這樣可以節約很多資源
初始化就是對類的靜態變量, 靜態代碼塊執行初始化操作
類初始化階段是類加載過程的最后一步, 前面的類加載過程中, 除了加載(Loading)階段用戶應用程序可以通過自定義類加載器參與之外, 其余動作完全由虛擬機主導和控制, 到了初始化階段, 才真正開始執行類中定義的Java程序代碼
初始化為類的靜態變量賦予正確的初始值, JVM負責對類進行初始化, 主要對類變量進行初始化, 在Java中對類變量進行初始值設定有兩種方式:
-
- 聲明靜態變量(類變量)時指定初始值
- 使用靜態代碼塊為類變量指定初始值
初始化步驟:
1. 假如這個類還沒有被加載和連接, 則程序先加載並連接該類
2. 假如該類的直接父類還沒有被初始化, 則先初始化其直接父類
3. 假如類中有初始化語句, 則系統依次執行這些初始化語句
JVM在堆內存中創建對象, 類的成員變量進入到堆內存中, 賦默認值
最后就是我們熟悉的Runtime運行時階段
Person p = new Person();
p,study();
執行上述代碼會在堆內存創建一個Person類的對象, 並且在棧內存分配一塊儲存空間存放Person類型的引用變量p, p存放該對象的地址並且指向該對象, 調用p的study方法實際是, 對象通過Person類的字節碼對象來訪問方法區Person字節碼的study方法
名詞解釋 :
Java源程序: 即Java源代碼, 用java語言編寫的程序
Java類加載器(Java Classloader): 是Java運行時環境(JRE)的一部分, 負責動態加載Java類到JVM的內存空間中
JRE: 即Java Runtime Environment, Java運行環境,內部包含了一個Java虛擬機以及一些標准類庫(Jar包)
JAR包: 通常用於聚合大量的Java類文件、相關的元數據和資源(文本、圖片等)文件到一個文件, 以便開發Java平台應用軟件或庫
JVM: 即Java Virtual Machine一種能夠運行Java字節碼(Java bytecode)的虛擬機
類(Class): 類是具有共同屬性和行為的對象的集合, 類定義了對象的屬性和方法
字節碼: 字節碼是已經經過編譯, 但與特定機器碼無關, 需要解釋器轉譯后才能成為機器碼的中間代碼
Java字節碼: 是Java虛擬機執行的一種指令格式
Java編譯器: 將Java源文件(.java文件)編譯成字節碼文件(.class文件, 是特殊的二進制文件, 二進制字節碼文件), 這種字節碼就是JVM的“機器語言”, javac命令可以簡單看成是Java編譯器
Java解釋器: 是JVM的一部分, Java解釋器用來解釋執行Java編譯器編譯后的程序, java命令可以簡單看成是Java解釋器
運行時類: 加載到內存中的字節碼文件對應的類稱為運行時類, 此運行時類即為一個Class的實例
Java堆(Heap): 在JVM啟動時創建, 是JVM所管理的內存中最大的一塊, 在JVM 中,堆(Heap)是可供各條線程共享的運行時內存區域, 也是供所有類實例和數組對象分配內存的區域, Java堆是被所有線程所共享的一塊內存區域, Java堆是垃圾收集器管理的主要區域
Java棧(Stack): 在函數中定義的基本類型的變量、Java指令代碼、對象的引用變量均在函數的棧內存中分配,當超過變量的作用域后,Java 會自動釋放掉該變量分配的內存空間
方法區(Non-Heap): 方法區與Java堆一樣,是各個線程共享的內存區域,用於存儲已被虛擬機加載的類信息(構造方法和接口定義)、常量、靜態變量、即時編譯器編譯后的代碼等數據, 運行時常量池存在方法區中