編譯期常量與運(yùn)行期常量的區(qū)別
如下代碼執(zhí)行后會(huì)輸出什么結(jié)果规哲?
public class MyTest3 {
public static void main(String[] args) {
System.out.println(MyParent3.str);
}
}
class MyParent3{
public static final String str = "hello";
static {
System.out.println("MyParent3 static code");
}
}
輸出
hello
修改后的代碼執(zhí)行后會(huì)輸出什么結(jié)果?
public class MyTest3 {
public static void main(String[] args) {
System.out.println(MyParent3.str);
}
}
class MyParent3{
public static final String str = UUID.randomUUID().toString();
static {
System.out.println("MyParent3 static code");
}
}
輸出
MyParent3 static code
fa328c0d-a230-4126-a720-c04d101f3dc2
小結(jié)
當(dāng)一個(gè)常量的值并非編譯期間可以確定的诽表,那么其值就不會(huì)被放到調(diào)用類(lèi)的常量池中,這時(shí)在運(yùn)行時(shí)唉锌,會(huì)導(dǎo)致主動(dòng)使用這個(gè)常量所在的類(lèi)隅肥,顯然會(huì)導(dǎo)致這個(gè)類(lèi)被初始化。
數(shù)組創(chuàng)建本質(zhì)分析
如下代碼執(zhí)行后會(huì)輸出什么呢袄简?靜態(tài)代碼塊是否會(huì)執(zhí)行腥放?
public class MyTest4 {
public static void main(String[] args) {
MyParent4 myParent4 = new MyParent4();//首次主動(dòng)使用
}
}
class MyParent4 {
static {
System.out.println("MyParent4 static block");
}
}
輸出:
MyParent4 static block
上述代碼是對(duì)類(lèi)主動(dòng)使用情況之一:創(chuàng)建類(lèi)的實(shí)例(首次主動(dòng)使用)
我們?cè)趧?chuàng)建一個(gè)類(lèi)的實(shí)例看下執(zhí)行結(jié)果,靜態(tài)代碼塊會(huì)執(zhí)行幾次痘番?
public class MyTest4 {
public static void main(String[] args) {
MyParent4 myParent4 = new MyParent4();//首次主動(dòng)使用
System.out.println("============");
MyParent4 myParent41 = new MyParent4();
}
}
class MyParent4 {
static {
System.out.println("MyParent4 static block");
}
}
輸出
MyParent4 static block
============
非首次對(duì)類(lèi)的主動(dòng)使用不會(huì)導(dǎo)致類(lèi)的初始化捉片。
如下示例代碼會(huì)輸出什么結(jié)果?靜態(tài)代碼塊是否會(huì)執(zhí)行汞舱?數(shù)組類(lèi)型是什么伍纫?
public class MyTest4 {
public static void main(String[] args) {
MyParent4[] myParent4s = new MyParent4[1];
System.out.println(myParent4s.getClass());
MyParent4[][] myParent4s1 = new MyParent4[1][1];
System.out.println(myParent4s1.getClass());
System.out.println(myParent4s.getClass().getSuperclass());
System.out.println(myParent4s1.getClass().getSuperclass());
}
}
class MyParent4 {
static {
System.out.println("MyParent4 static block");
}
}
輸出
class [Lcom.leofight.jvm.classloader.MyParent4;
class [[Lcom.leofight.jvm.classloader.MyParent4;
class java.lang.Object
class java.lang.Object
小結(jié)
對(duì)于數(shù)組實(shí)例來(lái)說(shuō),其類(lèi)型是由JVM在運(yùn)行期動(dòng)態(tài)生成的(類(lèi)似動(dòng)態(tài)代理)昂芜,表示為[Lcom.leofight.jvm.classloader.MyParent4這種形式莹规。動(dòng)態(tài)生成的類(lèi)型,其父類(lèi)型就是Object泌神。
對(duì)于數(shù)組來(lái)說(shuō)良漱,JavaDoc經(jīng)常將構(gòu)成數(shù)組的元素為Component,實(shí)際上就是將數(shù)組降低一個(gè)維度后的類(lèi)型欢际。
原生數(shù)據(jù)類(lèi)型數(shù)組對(duì)應(yīng)的數(shù)組類(lèi)型母市,示例代碼
public class MyTest4 {
public static void main(String[] args) {
int[] ints = new int[1];
System.out.println(ints.getClass());
System.out.println(ints.getClass().getSuperclass());
char[] chars = new char[1];
System.out.println(chars.getClass());
boolean[] booleans = new boolean[1];
System.out.println(booleans.getClass());
short[] shorts = new short[1];
System.out.println(shorts.getClass());
byte[] bytes = new byte[1];
System.out.println(bytes.getClass());
}
}
輸出:
class [I
class java.lang.Object
class [C
class [Z
class [S
class [B
助記符補(bǔ)充
anewarray:表示創(chuàng)建一個(gè)引用類(lèi)型的(如類(lèi)、接口损趋、數(shù)組)數(shù)組患久,并將其引用值壓入棧頂。
newarray:表示創(chuàng)建一個(gè)指定的原始類(lèi)型(如int浑槽、float蒋失、char等)的數(shù)組,并將其引用值壓入棧頂
接口初始化規(guī)則
public class MyTest5 {
public static void main(String[] args) {
System.out.println(MyChild5.b);
}
}
interface MyParent5 {
public static final int a = 5;
}
interface MyChild5 extends MyParent5 {
public static final int b = 6;
}
編譯后刪除MyParent5和MyChild5的class文件桐玻,執(zhí)行輸出
6
修改代碼如下:
public class MyTest5 {
public static void main(String[] args) {
System.out.println(MyChild5.b);
}
}
interface MyParent5 {
public static final int a = 5;
}
interface MyChild5 extends MyParent5 {
public static final int b = new Random().nextInt(2);
}
編譯后刪除MyParent5和MyChild5的class文件篙挽,執(zhí)行輸出
Exception in thread "main" java.lang.NoClassDefFoundError: com/leofight/jvm/classloader/MyChild5
Exception in thread "main" java.lang.NoClassDefFoundError: com/leofight/jvm/classloader/MyParent5
小結(jié)
當(dāng)一個(gè)接口在初始化時(shí),并不要求父接口都完成了初始化
只有在真正使用到父類(lèi)接口的時(shí)候(如引用接口中所定義的常量時(shí))镊靴,才會(huì)初始化
類(lèi)加載器準(zhǔn)備階段與初始化階段的重要意義分析
示例
public class MyTest6 {
public static void main(String[] args) {
Singleton singleton = Singleton.getSingleton();
System.out.println("counter1: " + Singleton.counter1);
System.out.println("counter2: " + Singleton.counter2);
}
}
class Singleton {
public static int counter1;
public static int counter2 = 0;
private static Singleton singleton = new Singleton();
private Singleton() {
counter1++;
counter2++;
}
public static Singleton getSingleton() {
return singleton;
}
}
輸出結(jié)果會(huì)是什么呢铣卡?
counter1: 1
counter2: 1
分析:
執(zhí)行Singleton.getSingleton()
會(huì)調(diào)用public static Singleton getSingleton() { return singleton; }
獲取Singleton
的實(shí)例,會(huì)調(diào)用代碼private static Singleton singleton = new Singleton();
接下來(lái)就會(huì)調(diào)用private Singleton() {counter1++; counter2++; }
,在調(diào)用構(gòu)造方法之前會(huì)給靜態(tài)變量賦值counter1=0偏竟,counter2=0算行;所以執(zhí)行為都為1。
調(diào)整上述代碼的順序苫耸,修改后代碼如下:
package com.leofight.jvm.classloader;
public class MyTest6 {
public static void main(String[] args) {
Singleton singleton = Singleton.getSingleton();
System.out.println("counter1: " + Singleton.counter1);
System.out.println("counter2: " + Singleton.counter2);
}
}
class Singleton {
public static int counter1;
private static Singleton singleton = new Singleton();
private Singleton() {
counter1++;
counter2++;//準(zhǔn)備階段的重要意義
}
public static int counter2 = 0;
public static Singleton getSingleton() {
return singleton;
}
}
輸出結(jié)果
counter1: 1
counter2: 0
分析:
靜態(tài)變量初始化是按照聲明的順序初始化的,public static int counter1
counter1初始化的初值為0(準(zhǔn)備階段給的默認(rèn)值)儡陨,然后private static Singleton singleton = new Singleton();
初始化會(huì)調(diào)用構(gòu)造方法private Singleton() { counter1++; counter2++; }
這里引用到了counter2褪子,counter2在準(zhǔn)備階段賦了默認(rèn)值0量淌,所以在這個(gè)階段,counter1嫌褪,counter2 都為1呀枢,繼續(xù)初始化public static int counter2 = 0;
顯式的給counter2賦初值為0,所以counter1 =1笼痛,counter=0.
類(lèi)加載器深入解析及重要特性剖析
加載:就是把二進(jìn)制形式的java類(lèi)型讀入java虛擬機(jī)中
驗(yàn)證:類(lèi)文件的結(jié)構(gòu)檢查裙秋、語(yǔ)義檢查、字節(jié)碼驗(yàn)證缨伊、二進(jìn)制兼容性的驗(yàn)證
準(zhǔn)備:為類(lèi)變量分配內(nèi)存摘刑,設(shè)置默認(rèn)值。但是在到達(dá)初始化之前刻坊,類(lèi)變量沒(méi)有初始化為真正的初始化值
解析:解析過(guò)程就是類(lèi)型的常理池中尋找類(lèi)枷恕、接口、字段和方法的符號(hào)引用谭胚,把這些符號(hào)引用替換成直接引用的過(guò)程
初始化:為類(lèi)變量賦予正確的初始值
類(lèi)實(shí)例化:
為新的對(duì)象分配內(nèi)存
為實(shí)例變量賦默認(rèn)值
為實(shí)例變量賦正確的初始值
java編譯器為它編譯的每一個(gè)類(lèi)都至少生成一個(gè)實(shí)例初始化方法徐块,在java的class文件中,這個(gè)實(shí)例初始化方法被稱(chēng)為“<init>"灾而。針對(duì)源代碼中每一個(gè)類(lèi)的構(gòu)造方法胡控,java編譯器都產(chǎn)生一個(gè)<init>方法。
類(lèi)的加載的最終產(chǎn)品是位于內(nèi)存中的Class對(duì)象
Class對(duì)象封裝了類(lèi)的方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)旁趟,并且向Java程序員提供了訪問(wèn)方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)的接口昼激。
有兩種類(lèi)型的類(lèi)加載器
- Java虛擬機(jī)自帶的加載器
①根類(lèi)加載器(Bootstrap)
②擴(kuò)展類(lèi)加載器(Extension)
③系統(tǒng)(應(yīng)用)類(lèi)加載器(System) - 用戶(hù)自定義的類(lèi)加載器
①java.lang.ClassLoader的子類(lèi)
② 用戶(hù)可以定制類(lèi)的加載方式
類(lèi)加載器并不需要等到某個(gè)類(lèi)被“首次主動(dòng)使用”時(shí)再加載它
JVM規(guī)范允許類(lèi)加載器在預(yù)料某個(gè)類(lèi)將要被使用時(shí)就預(yù)先加載它,如果在預(yù)先加載的過(guò)程中遇到了.class文件缺失或者存在錯(cuò)誤轻庆,類(lèi)加載器必須在程序首次主動(dòng)使用該類(lèi)時(shí)才報(bào)告錯(cuò)誤(LinkageError錯(cuò)誤)
如果這個(gè)類(lèi)一直沒(méi)有被程序主動(dòng)使用癣猾,那么類(lèi)加載器就不會(huì)報(bào)告錯(cuò)誤。
類(lèi)的驗(yàn)證
類(lèi)被加載后余爆,就進(jìn)入連接階段纷宇,連接就是將已經(jīng)讀入到內(nèi)存的類(lèi)的二進(jìn)制數(shù)據(jù)合并到虛擬機(jī)的允許時(shí)環(huán)境中去。
類(lèi)的驗(yàn)證的內(nèi)容
①類(lèi)文件的結(jié)構(gòu)檢查
②語(yǔ)義檢查
③字節(jié)碼驗(yàn)證
④二進(jìn)制兼容性的驗(yàn)證
在準(zhǔn)備階段蛾方,Java虛擬機(jī)為類(lèi)的靜態(tài)變量分配內(nèi)存像捶,并設(shè)置默認(rèn)的初始值。例如對(duì)于以下Sample類(lèi)桩砰,在準(zhǔn)備階段拓春,將為int類(lèi)型的靜態(tài)變量a分配4個(gè)字節(jié)的內(nèi)存空間,并且賦予默認(rèn)值0亚隅,為long類(lèi)型的靜態(tài)變量b分配8個(gè)字節(jié)的內(nèi)存空間硼莽,并且賦予默認(rèn)值0.
public class Sample {
private static int a = 1;
public static long b;
static {
b = 2;
}
...
}
在初始化階段,Java虛擬機(jī)執(zhí)行類(lèi)的初始化語(yǔ)句煮纵,為類(lèi)的靜態(tài)變量賦予初始值懂鸵。在程序中偏螺,靜態(tài)變量的初始化有兩種途徑:(1)在靜態(tài)變量的聲明處進(jìn)行初始化;(2)在靜態(tài)代碼塊中進(jìn)行初始化匆光。例如在以下代碼中套像,靜態(tài)變量a和b都被顯式初始化,而靜態(tài)變量c沒(méi)有被顯式初始化终息,它將保持默認(rèn)值0夺巩。
public class Sample {
private static int a = 1;//在靜態(tài)變量的聲明處進(jìn)行初始化
public static long b;
public static long c;
static {
b = 2;//在靜態(tài)代碼塊中進(jìn)行初始化
}
...
}
靜態(tài)變量的聲明語(yǔ)句,以及靜態(tài)代碼塊都被看做類(lèi)的初始化語(yǔ)句,Java虛擬機(jī)會(huì)按照初始化語(yǔ)句的類(lèi)文件中的先后順序來(lái)依次執(zhí)行它們。例如一下Sample類(lèi)初始化后倒戏,它的靜態(tài)變量a的取值為4.
package com.leofight.jvm.classloader;
public class Sample {
static int a = 1;
static {
a = 2;
}
static {
a = 4;
}
public static void main(String args[]) {
System.out.println("a=" + a);//打印a=4
}
}
類(lèi)的初始化
類(lèi)的初始化步驟
- 假如這個(gè)類(lèi)還沒(méi)有被加載和連接然磷,那就先進(jìn)行加載和連接
- 假如類(lèi)存在直接父類(lèi),并且這個(gè)父類(lèi)還沒(méi)有被初始化,那就先初始化直接父類(lèi)。
- 假如類(lèi)中存在初始化語(yǔ)句,那就依次執(zhí)行這些初始化語(yǔ)句人柿。
類(lèi)的初始化時(shí)機(jī)
- 主動(dòng)使用
- 被動(dòng)使用
詳細(xì)內(nèi)容見(jiàn)上一篇文章
當(dāng)Java虛擬機(jī)初始化一個(gè)類(lèi)時(shí),要求它的所有父類(lèi)都已經(jīng)被初始化忙厌,但是這條規(guī)則并不使用于接口凫岖。
- 在初始化一個(gè)類(lèi)時(shí),并不會(huì)先初始化它所實(shí)現(xiàn)的接口逢净。
- 在初始化一個(gè)接口時(shí)哥放,并不會(huì)先初始化它的父接口。
因此爹土,一個(gè)父接口并不會(huì)因?yàn)樗淖咏涌诨蛘邔?shí)現(xiàn)類(lèi)的初始化而初始化甥雕。只有當(dāng)程序首次使用特點(diǎn)接口的靜態(tài)變量時(shí),才會(huì)導(dǎo)致該接口的初始化胀茵。
只有當(dāng)程序訪問(wèn)的靜態(tài)變量或者靜態(tài)方法確實(shí)在當(dāng)前類(lèi)或者當(dāng)前接口中定義時(shí)社露,才可以認(rèn)為是對(duì)類(lèi)或者接口的主動(dòng)使用。
調(diào)用ClassLoader類(lèi)的loadClass方法加載一個(gè)類(lèi)琼娘,并不是對(duì)類(lèi)的主動(dòng)使用峭弟,不會(huì)導(dǎo)致類(lèi)的初始化。
類(lèi)加載器用來(lái)把類(lèi)加載到Java虛擬機(jī)中脱拼。從JDK1.2版本開(kāi)始瞒瘸,類(lèi)的加載過(guò)程采用父親委托機(jī)制,這種機(jī)制能更好地保證Java平臺(tái)的安全熄浓。在此委托機(jī)制中情臭,除了Java虛擬機(jī)自帶的根類(lèi)加載器以外,其余的類(lèi)加載器都有且只有一個(gè)父加載器。當(dāng)Java程序請(qǐng)求加載器loader1加載Sample類(lèi)時(shí)俯在,loader1首先委托自己的父類(lèi)加載器去加載Sample類(lèi)丁侄,若父加載器能加載,則由父加載器完成加載任務(wù)朝巫,否則才由加載器loader1本身加載Sample類(lèi)。
Java虛擬機(jī)自帶了以下幾種加載器石景。
根(Bootstrap)類(lèi)加載器:該加載器沒(méi)有父加載器劈猿。它負(fù)責(zé)加載虛擬機(jī)的核心庫(kù),如java.lang.*等潮孽。例如從例程(Sample.java)可以看出揪荣,java.lang.Object就是由根類(lèi)加載器加載的。根類(lèi)加載器從系統(tǒng)屬性sun.boot.class.path所指定的目錄中加載類(lèi)庫(kù)往史。根類(lèi)加載器的實(shí)現(xiàn)依賴(lài)于底層操作系統(tǒng)仗颈,屬性虛擬機(jī)的實(shí)現(xiàn)的一部分,它并沒(méi)有繼承java.lang.ClassLoader類(lèi)椎例。
擴(kuò)展(Extension)類(lèi)加載器:它的父類(lèi)加載器就是根類(lèi)加載器挨决。它從java.ext.dirs系統(tǒng)屬性所指定的目錄中加載類(lèi)庫(kù),或者從JDK的安裝目錄的jre\lib\ext子目錄(擴(kuò)展目錄)下加載類(lèi)庫(kù)订歪,如果把用戶(hù)創(chuàng)建的JAR文件放在這個(gè)目錄下脖祈,也會(huì)自動(dòng)有擴(kuò)展類(lèi)加載器加載。擴(kuò)展類(lèi)加載器是純Java類(lèi)刷晋,是java.lang.ClassLoader類(lèi)的子類(lèi)盖高。
系統(tǒng)(System)類(lèi)加載器:也稱(chēng)為應(yīng)用類(lèi)加載器,它的父加載器為擴(kuò)展類(lèi)加載器眼虱。它從環(huán)境變量classpath或者系統(tǒng)屬性java.class.path所指定的目錄中加載類(lèi)喻奥,它是用戶(hù)自定義的類(lèi)加載器的默認(rèn)父加載器。系統(tǒng)類(lèi)加載器是純Java類(lèi)捏悬,是java.lang.ClassLoader類(lèi)的子類(lèi)撞蚕。
除了以上虛擬機(jī)自帶的加載器外,用戶(hù)還可以定制自己的類(lèi)加載器邮破。Java提供了抽象類(lèi)java.lang.ClassLoader,所有用戶(hù)自定義類(lèi)的加載器都應(yīng)該繼承ClassLoader類(lèi)诈豌。