大綱:
JVM
一、編譯機(jī)制
二、類加載機(jī)制(裝載、驗(yàn)證、準(zhǔn)備、解析、初始化、使用、卸載)
三、類執(zhí)行機(jī)制
源碼編譯機(jī)制:
(由.java源文件轉(zhuǎn)為.class二進(jìn)制字節(jié)碼文件的過程)
使用命令 javac test.java 就可以編譯test.java文件。生成test.class文件。
編譯的過程:
詞法分析、語法分析、語義分析、生成字節(jié)碼
詳細(xì)的過程:
源代碼文件*.java -> 詞法分析器 -> tokens流 -> 語法分析器 -> 語法樹/抽象語法樹 -> 語義分析器 -> 注解抽象語法樹 -> 字節(jié)碼生成器 -> JVM字節(jié)碼文件*.class
———————————————————————————————————————————
類加載機(jī)制:
在Class文件中描述的各種信息,最終都需要加載到虛擬機(jī)中才能運(yùn)行和使用。那么虛擬機(jī)是如何加載這些Class文件的呢?
JVM把描述類數(shù)據(jù)的字節(jié)碼.Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的java類型,這就是虛擬機(jī)的類加載機(jī)制。
類從被加載到虛擬機(jī)內(nèi)存中開始,到卸載出內(nèi)存為止,它的生命周期包括了:加載/裝載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸載(Unloading)七個(gè)階段,其中驗(yàn)證、準(zhǔn)備、解析三個(gè)部分統(tǒng)稱鏈接。
一、加載
a.(類的加載指的是將類的.class文件中的二進(jìn)制數(shù)據(jù)讀入到內(nèi)存中,生成對(duì)應(yīng)的class對(duì)象)我們可以利用類加載器,實(shí)現(xiàn)類的動(dòng)態(tài)加載。
b.在Java中,采用雙親委派機(jī)制來實(shí)現(xiàn)類的加載。委托模式。每個(gè) ClassLoader 都有一個(gè)父加載器。類加載器在加載類之前會(huì)先遞歸的去嘗試使用父加載器加載。父類委托,先讓父類加載器試圖加載該類,只有在父類加載器無法加載該類的時(shí)候才試圖從自己的類路徑加載,從而保證只有一個(gè)類進(jìn)行加載,虛擬機(jī)有一個(gè)內(nèi)建的啟動(dòng)類加載器(bootstrap ClassLoader),該加載器沒有父加載器,但是可以作為其他加載器的父加載器。
c.委派機(jī)制則保證了基類都由相同的類加載器加載,這樣就避免了同一個(gè)字節(jié)碼文件被多次加載生成不同的 Class 對(duì)象的問題。
d.類加載器其實(shí)也是Java類。有四大類:
根加載器Bootstrap Class Loader:其負(fù)責(zé)加載Java的核心類,比如String、System這些類
擴(kuò)展加載器Extension Class Loader:其負(fù)責(zé)加載JRE的拓展類庫(kù)
系統(tǒng)應(yīng)用加載器APP Class Loader:其負(fù)責(zé)加載CLASSPATH環(huán)境變量所指定的JAR包和類路徑
用戶自定義加載器Customer Class Loader
e.加載過程中會(huì)先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個(gè)classloader已加載就視為已加載此類,保證此類只所ClassLoader加載一次。然后開始加載類,加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。
f.java程序運(yùn)行的場(chǎng)所是內(nèi)存,當(dāng)在命令行下執(zhí)行:java HelloWorld命令的時(shí)候,JVM會(huì)將HelloWorld.class加載到內(nèi)存中,并形成一個(gè)Class的對(duì)象HelloWorld.class。
g.類加載的方式:
命令行啟動(dòng)應(yīng)用時(shí)候由JVM初始化加載
通過Class.forName()方法動(dòng)態(tài)加載
通過ClassLoader.loadClass()方法動(dòng)態(tài)加載
h.雙親委派模型的工作過程為:如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的加載器都是如此,因此所有的類加載請(qǐng)求都會(huì)傳給頂層的啟動(dòng)類加載器,只有當(dāng)父加載器反饋?zhàn)约簾o法完成該加載請(qǐng)求(該加載器的搜索范圍中沒有找到對(duì)應(yīng)的類)時(shí),子加載器才會(huì)嘗試自己去加載。
二、鏈接
當(dāng)類被加載后,系統(tǒng)會(huì)為之生成一個(gè)Class對(duì)象,接著將會(huì)進(jìn)入連接階段,鏈接階段負(fù)責(zé)把類的二進(jìn)制數(shù)據(jù)合并到JRE中
三個(gè)階段
驗(yàn)證:檢驗(yàn)被加載的類是否有正確的內(nèi)部結(jié)構(gòu),并和其他類協(xié)調(diào)一致
準(zhǔn)備:負(fù)責(zé)為類的類(靜態(tài))變量分配內(nèi)存。并設(shè)置默認(rèn)初始值
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些內(nèi)存都將在方法區(qū)中分配。對(duì)于該階段有以下幾點(diǎn)需要注意:
1、這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(static),而不包括實(shí)例變量,實(shí)例變量會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一塊分配在Java堆中。
2、這里所設(shè)置的初始值通常情況下是數(shù)據(jù)類型默認(rèn)的零值(如0、0L、null、false等),而不是被在Java代碼中被顯式地賦予的值。
假設(shè)一個(gè)類變量的定義為:public static int value = 3;
那么變量value在準(zhǔn)備階段過后的初始值為0,而不是3,因?yàn)檫@時(shí)候尚未開始執(zhí)行任何Java方法,而把value賦值為3的putstatic指令是在程序編譯后,存放于類構(gòu)造器()方法之中的,所以把value賦值為3的動(dòng)作將在初始化階段才會(huì)執(zhí)行。
解析:將類的二進(jìn)制數(shù)據(jù)中的符號(hào)引用轉(zhuǎn)換成直接引用
三、初始化
初始化,為類的靜態(tài)變量賦予正確的初始值,JVM負(fù)責(zé)對(duì)類進(jìn)行初始化,主要對(duì)類變量進(jìn)行初始化。
在Java中對(duì)類變量進(jìn)行初始值設(shè)定有兩種方式:
①聲明類變量是指定初始值
②使用靜態(tài)代碼塊為類變量指定初始值
JVM初始化步驟
假如這個(gè)類還沒有被加載和連接,則程序先加載并連接該類
假如該類的直接父類還沒有被初始化,則先初始化其直接父類
假如類中有初始化語句,則系統(tǒng)依次執(zhí)行這些初始化語句
類初始化時(shí)機(jī)
創(chuàng)建類實(shí)例。也就是new的方式
調(diào)用某個(gè)類的類方法
訪問某個(gè)類或接口的類變量,或?yàn)樵擃愖兞抠x值
使用反射方式強(qiáng)制創(chuàng)建某個(gè)類或接口對(duì)應(yīng)的java.lang.Class對(duì)象
初始化某個(gè)類的子類,則其父類也會(huì)被初始化
直接使用java.exe命令來運(yùn)行某個(gè)主類
類實(shí)例創(chuàng)建過程
按照父子繼承關(guān)系進(jìn)行初始化,首先執(zhí)行父類的初始化塊部分,然后是父類的構(gòu)造方法;再執(zhí)行本類繼承的子類的初始化塊,最后是子類的構(gòu)造方法
注意??:
JAVA類首次裝入時(shí),會(huì)對(duì)靜態(tài)成員變量或方法進(jìn)行一次初始化,但方法不被調(diào)用是不會(huì)執(zhí)行的, 靜態(tài)成員變量和靜態(tài)初始化塊級(jí)別相同,非靜態(tài)成員變量和非靜態(tài)初始化塊級(jí)別相同。(鏈接的準(zhǔn)備階段)
初始化階段:
先初始化父類的靜態(tài)代碼--->初始化子類的靜態(tài)代碼-->
初始化父類的非靜態(tài)代碼--->初始化父類構(gòu)造函數(shù)--->
初始化子類非靜態(tài)代碼—>初始化子類構(gòu)造函數(shù)
分析:靜態(tài)代碼塊在類加載的時(shí)候執(zhí)行,而非靜態(tài)代碼快在生成對(duì)象時(shí)才被執(zhí)行
類執(zhí)行機(jī)制:
JVM是基于棧結(jié)構(gòu)的體系結(jié)構(gòu)來執(zhí)行class字節(jié)碼的,不同于windows和Linux基于寄存器結(jié)構(gòu)。類的執(zhí)行機(jī)制,主要是在Java棧上面完成。當(dāng)一個(gè)線程被創(chuàng)建后,Java棧和PC寄存器就會(huì)被創(chuàng)建。Java棧由棧幀組成,調(diào)用一個(gè)方法,就會(huì)生成一個(gè)棧幀(可以理解為表示調(diào)用一個(gè)方法)。棧幀又由局部變量表、操作數(shù)棧和常量池引用組成。
其他:
常量:
一、用final修飾的成員變量表示常量,值一旦給定就無法改變!一般都用大寫字符為常量賦值。在常量中,往往通過下劃線來分隔不同的字符。對(duì)于同時(shí)被static和final修飾的常量,必須在聲明的時(shí)候就為其顯式地賦值,否則編譯時(shí)不通過;而只被final修飾的常量則既可以在聲明時(shí)顯式地為其賦值,也可以在類初始化時(shí)顯式地為其賦值,總之,在使用前必須為其顯式地賦值,系統(tǒng)不會(huì)為其賦予默認(rèn)零值。
二、final關(guān)鍵字與static關(guān)鍵字同時(shí)使用
由于Java是面向?qū)ο蟮恼Z言,所以在Java常量定義的時(shí)候還有與其它編程語言不同的地方。如一段程序代碼從編輯到最后執(zhí)行,即使需要經(jīng)過兩個(gè)過程,分別為代碼的裝載與對(duì)象的建立。不同的過程對(duì)于常量的影響是不同的。
1).不使用static修飾情況:
例如:final long CURRENT_TIME=System.currentTimeMillis();
默認(rèn)情況下,定義的常量是在對(duì)象建立的時(shí)候被初始化。如果在建立常量時(shí),直接賦一個(gè)固定的值,而不是通過其他對(duì)象或者函數(shù)來賦值,那么這個(gè)常量的值就是恒定不變的,即在多個(gè)對(duì)象中值也使相同的。但是如果在給常量賦值的時(shí)候,采用的是一些函數(shù)或者對(duì)象(如生成隨機(jī)數(shù)的Random對(duì)象),那么每次建立對(duì)象時(shí)其給常量的初始化值就有可能不同。可見,使用final的Java常量定義并不是恒定不變的。
2).使用static修飾情況:
例如:static final long CURRENT_TIME=System.currentTimeMillis();
這個(gè)是一個(gè)靜態(tài)的概念。即當(dāng)利用這個(gè)關(guān)鍵字來修飾一個(gè)變量的時(shí)候,在創(chuàng)建對(duì)象之前就會(huì)為這個(gè)變量在內(nèi)存中創(chuàng)建一個(gè)存儲(chǔ)空間。以后創(chuàng)建對(duì)對(duì)象如果需要用到這個(gè)靜態(tài)變量,那么就會(huì)共享這一個(gè)變量的存儲(chǔ)空間。也就是說,在創(chuàng)建對(duì)象的時(shí)候,如果用到這個(gè)變量,那么系統(tǒng)不會(huì)為其再分配一個(gè)存儲(chǔ)空間,而只是將這個(gè)內(nèi)存存儲(chǔ)空間的地址賦值給他。如此做的好處就是可以讓多個(gè)對(duì)象采用相同的初始變量。當(dāng)需要改變多個(gè)對(duì)象中變量值的時(shí)候,只需要改變一次即可。從這個(gè)特性上來說,其跟常量的作用比較類似。不過其并不能夠取代常量的作用。
變量
類變量:static int allClicks=0;
局部變量:類的方法中的變量
public void method(){
int i =0; //局部變量
}
實(shí)例變量: String str="hello world";