類的加載、連接和初始化
1.JVM和類
當(dāng)調(diào)用java命令運(yùn)行某個(gè)java程序時(shí)镀赌,該命令會(huì)啟動(dòng)一個(gè)java虛擬機(jī)進(jìn)程音念,不管該Java程序有多么復(fù)雜沪饺,該程序啟動(dòng)了多少個(gè)線程,它們都處于該java虛擬機(jī)進(jìn)程里闷愤。
當(dāng)系統(tǒng)出現(xiàn)以下幾種情況時(shí)整葡,JVM進(jìn)程將被終止:
(1)程序運(yùn)行到最后正常結(jié)束
(2)使用 Systerm.exit()或者 Runtime.getRuntime().exit()
(3)執(zhí)行過程中遇到未捕獲的異常或者錯(cuò)誤而結(jié)束
(4)程序所在平臺(tái)強(qiáng)制結(jié)束JVM進(jìn)程
例子:
A.java
package com.example;
public class A
{
//類變量
public static int a = 5;
public static void main(String[] args)
{
}
}
AText.java
package com.example;
/**
* Created by zhoujian on 2017/4/3.
*/
public class AText
{
public static void main(String[] args)
{
A a = new A();
a.a++;
//運(yùn)行結(jié)果:6
System.out.println(a.a);
}
}
BText.java
package com.example;
/**
* Created by zhoujian on 2017/4/3.
*/
public class BText
{
public static void main(String[] args)
{
A b = new A();
//運(yùn)行結(jié)果:5
System.out.println(b.a);
}
}
兩次運(yùn)行讥脐,處于不同的JVM中遭居,兩個(gè)JVM之間并不會(huì)共享數(shù)據(jù)
2.類的加載
當(dāng)程序使用某個(gè)類時(shí),如果該類該類還未被加載到內(nèi)存中旬渠,則系統(tǒng)會(huì)通過加載魏滚、連接和初始化這三個(gè)步驟來對(duì)該類進(jìn)行初始化。這三個(gè)步驟稱為類加載或類初始化
類加載指的是將類的class文件讀入內(nèi)存坟漱,并為之創(chuàng)建一個(gè)java.lang.Class對(duì)象
類的加載由類加載器完成鼠次,類加載器通常由JVM提供
通過不同的類加載器,可以從不同來源加載類的二進(jìn)制數(shù)據(jù)
- 從本地系統(tǒng)加載class 文件
- 從JAR包加載class文件
- 通過網(wǎng)絡(luò)加載class文件
- 把一個(gè)java 文件動(dòng)態(tài)編譯芋齿,并執(zhí)行加載
類加載器無需等到使用時(shí)才去加載該類腥寇,java 虛擬機(jī)規(guī)范允許系統(tǒng)預(yù)先加載某些類
3.類的連接
當(dāng)類被加載之后,系統(tǒng)為之生成一個(gè)對(duì)應(yīng)的Class 對(duì)象觅捆,接著將進(jìn)入連接階段赦役,連接階段負(fù)責(zé)把二進(jìn)制數(shù)據(jù)合并到JRE中。類連接又分為三個(gè)階段
- 驗(yàn)證:用于檢驗(yàn)被加載的類是否有正確的內(nèi)部結(jié)構(gòu)栅炒,并和其他類協(xié)調(diào)一致
- 準(zhǔn)備:類準(zhǔn)備階段負(fù)責(zé)為類的類變量分配內(nèi)存掂摔,并設(shè)置默認(rèn)初始值
- 解析:將類的二進(jìn)制數(shù)據(jù)中的符號(hào)引用替換成直接引用
4.類的初始化
在類的初始化階段,虛擬機(jī)負(fù)責(zé)對(duì)類進(jìn)行初始化赢赊,主要是對(duì)類變量初始化乙漓。
在java類中對(duì)變量指定初始值有兩種方式:
- 聲明類變量時(shí)指定初始值
- 使用靜態(tài)初始化塊為類變量指定初始值
package com.example;
/**
* Created by zhoujian on 2017/4/3.
*/
public class AText
{
//聲明變量 b時(shí)指定初始值
static int b = 2;
static
{
//使用靜態(tài)初始化塊為變量b指定初始值
b = 5;
}
public static void main(String[] args)
{
A a = new A();
a.a++;
System.out.println(a.a);
System.out.println(AText.b);
}
}
5.類初始化的時(shí)機(jī)
當(dāng) Java 程序首次通過下面6種方式來使用某個(gè)類或者接口,系統(tǒng)就會(huì)初始化該類或者接口
- 創(chuàng)建類的實(shí)例
- 調(diào)用某個(gè)類的方法(靜態(tài)方法)
- 訪問某個(gè)類或接口的類變量释移,或者為該類變量賦值
- 使用反射的方式來強(qiáng)制創(chuàng)建某個(gè)類或者接口對(duì)應(yīng)的 java.lang.Class 對(duì)象
- 初始化某個(gè)類的子類叭披。那么該子類所有父類都會(huì)被初始化
- 直接使用java.exe 命令來運(yùn)行某個(gè)主類
對(duì)于一個(gè)final 型的類變量,如果該類變量在編譯的時(shí)就可以確定下來玩讳,那么這個(gè)類變量相當(dāng)于“宏變量”涩蜘。java 編譯器會(huì)在編譯時(shí)直接把這個(gè)類變量出現(xiàn)的地方替換成它的值,因此即使程序使用該靜態(tài)類變量熏纯,也不會(huì)導(dǎo)致該類的初始化
package com.example;
public class MyTest
{
static
{
//靜態(tài)初始化塊
}
//變量name可以在編譯的時(shí)候確定下來同诫,相當(dāng)于一個(gè)常量
static final String name = "zhoujian";
public static void main(String[] args)
{
//不會(huì)初始化MyTest類
System.out.println(MyTest.name);
}
}
當(dāng)使用ClassLoader 類的loadClass()方法來加載某個(gè)類時(shí),該方法只是加載類樟澜,并不會(huì)執(zhí)行該類的初始化误窖。
使用Class的forName()靜態(tài)方法才會(huì)導(dǎo)致強(qiáng)制初始化該類
類加載器
類加載器負(fù)責(zé)將.class 文件加載到內(nèi)存中叮盘,并為之生成對(duì)應(yīng)的java.lang.Class 對(duì)象
1. 類加載器簡(jiǎn)介
類加載器負(fù)責(zé)加載所有的類,系統(tǒng)為所有被載入內(nèi)存中的類生成一個(gè)java.lang.Class 實(shí)例
當(dāng)JVM啟動(dòng)時(shí)贩猎,會(huì)形成由三個(gè)類加載器組成的初始類加載器層次結(jié)構(gòu)
- Bootstrap ClassLoader:根類加載器
- Extension ClassLoader:擴(kuò)展類加載器
- System ClassLoader:系統(tǒng)類加載器
例子:獲取根類加載器所加載的核心類庫
package com.example;
import java.net.URL;
/**
* Created by zhoujian on 2017/4/3.
*/
public class Text
{
public static void main(String[] args)
{
//獲取根類加載器所加載的全部URL數(shù)組
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++)
{
//輸出根類加載器的全部url
System.out.println(urls[i].toExternalForm());
}
}
}
Extension ClassLoader:擴(kuò)展類加載器,它負(fù)責(zé)加載JRE的擴(kuò)展目錄(%JAVA_HOME%jre/lib/ext或者由java.ext.dirs系統(tǒng)屬性指定的目錄)中JAR包的類
System ClassLoader:系統(tǒng)類加載器熊户,可以通過ClassLoader的靜態(tài)方法 getSystemClassLoader()來獲取系統(tǒng)類加載器
2. 類加載機(jī)制
JVM的類加載機(jī)制主要要三種:
- 全盤負(fù)責(zé) :就是當(dāng)一個(gè)類加載器負(fù)責(zé)加載某個(gè)Class時(shí)萍膛,該Class所依賴的和引用的其它Class也將由該類加載器負(fù)責(zé)載入
- 父類委托:先讓父類加載器試圖加載該Class吭服,只有在父類加載器無法加載該類時(shí)才嘗試從自己的類路徑中加載該類
- 緩存機(jī)制:所有加載過得Class都會(huì)被緩存,當(dāng)程序中需要使用某個(gè)Class時(shí)蝗罗,類加載器先從緩存中獲取艇棕,只要緩存中不存在該Class對(duì)象時(shí),系統(tǒng)才會(huì)讀取對(duì)應(yīng)的二進(jìn)制數(shù)據(jù)串塑,并轉(zhuǎn)換成Class對(duì)象沼琉,存入緩存中。