轉(zhuǎn)載、引用請標明出處
http://www.reibang.com/p/853701433b3a
本文出自zhh_happig的簡書博客拉庶,謝謝
以下內(nèi)容嗜憔,是本人學習筆記和工作中的總結(jié),僅供大家參考氏仗,有誤的地方還請指正
java虛擬機
執(zhí)行一個java程序吉捶,都會啟動一個java虛擬機的進程,進程里面包含一個主線程來執(zhí)行程序皆尔,當程序執(zhí)行完了之后呐舔,java虛擬機進程就消亡了。
在如下幾種情況下慷蠕,java虛擬機將結(jié)束生命周期
- 執(zhí)行了System.exit()方法
- 程序正常執(zhí)行結(jié)束
- 程序在執(zhí)行過程中遇到了異成浩矗或錯誤而異常終止
- 由于操作系統(tǒng)出現(xiàn)錯誤而導(dǎo)致java虛擬機進程終止
一 類的加載、連接流炕、初始化
1 加載:查找并加載類的二進制數(shù)據(jù)
- 類記載器ClassLoader將java的.class文件中的二進制數(shù)據(jù)讀入到內(nèi)存中澎现,將其放在運行時數(shù)據(jù)區(qū)的方法區(qū)中
- 加載后,虛擬機在堆區(qū)創(chuàng)建一個與該類對應(yīng)java.lang.Class對象浪感,不管類的對象有多少個昔头,與此類對應(yīng)的Class對象只有一個,Class對象用來封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)影兽,所以類里面的內(nèi)容都可以通過Class對象獲取揭斧。Class對象是反射的入口。
- 類的加載并不是你首次使用的時候去加載峻堰,而是預(yù)料到某個類要被使用的時候預(yù)先加載它讹开。
- 程序只有被加載到內(nèi)存中,才能被執(zhí)行
2 連接
- 驗證:確保被加載的類的正確性
- 通過javac生成的.class文件肯定是正確的捐名,但是有的直接手動生成.calss可能是不符合java字節(jié)碼規(guī)則的旦万,這里要驗證。
- 準備:為類的靜態(tài)變量分配內(nèi)存镶蹋,并將其初始化為默認值
- 到這一步成艘,類的加載、連接贺归、初始化還沒有完成淆两,不會生成對象,所以所有的實例對象都不會分配內(nèi)存拂酣,只有靜態(tài)變量會被分配內(nèi)存秋冰,初始化為默認值:int型初始化為0,boolean類型初始化為false婶熬,引用類型初始化為null剑勾。順序:從上至下埃撵。
- 解析:把類中的符號引用轉(zhuǎn)換為直接引用
- 在Worker類中調(diào)用car.run(),Car類的run()方法虽另。car.run()---這是符號引用暂刘,在解析階段java虛擬機會把這個符號引用替換為一個指針,該指針指向了Car類的run()方法在方法區(qū)內(nèi)的內(nèi)存位置洲赵,這個指針就是直接引用鸳惯。
3 初始化:為類的靜態(tài)變量賦予正確的初始值
- 正確的初始值是在程序中被賦的值商蕴,比如 int a = 3叠萍,這一步將3賦值給a;所以a經(jīng)過了兩次賦值:第一次是連接準備階段绪商,a先會被賦默認初始值0苛谷,第二就是初始化為程序中賦的值。
- 靜態(tài)變量的初始化有兩種方式
- 在靜態(tài)變量的聲明處進項初始化
- 在靜態(tài)代碼塊中進行初始化
- 初始化步驟
- 假如這個類還沒有被加載和連接格郁,先進行加載和連接
- 假如這個類存在父類腹殿,父類還沒有初始化,先初始化直接的父類
- 初始化語句執(zhí)行順序:從上至下依次執(zhí)行
- 初始化時機
- 類的主動使用
- 當java虛擬機初始化類時例书,它所有的父類都已經(jīng)被初始化了锣尉,但是這條規(guī)則不適用與接口
- 在初始化一個類時,并不會先初始化它實現(xiàn)的接口
- 初始化一個接口時决采,并不會初始化它的父接口
- 只有當程序首次使用特定接口的靜態(tài)變量時自沧,才會導(dǎo)致該接口的初始化
二 java程序?qū)︻惖氖褂梅绞?/h1>
主動使用
被動使用
主動使用(6種),除了以下6種树瞭,其他類的使用全是被動使用
- 創(chuàng)建類的實例
- new出來一個類的實例對象拇厢,即為對這個類的主動使用
- 訪問某個類或接口的靜態(tài)變量,或者對該靜態(tài)變量賦值
- int b = Test.a; 這個也是對Test類的主動使用
- final修飾的靜態(tài)變量要注意:如果這個變量在編譯時就能確定它的值晒喷,就不會導(dǎo)致類被初始化孝偎,例如 public static final int a = 6/3
- 如果這個變量要在運行時才能確定它的值,才會導(dǎo)致類被初始化凉敲,例如public static final int a = new Random().nextInt(100)
- 調(diào)用類的靜態(tài)方法
- int b = Test.add()
- 只有當訪問的靜態(tài)變量或靜態(tài)方法確實在當前類或當前接口中定義時衣盾,才可以認為是對類和接口的主動使用,詳見練習題5中例子爷抓。
- 反射創(chuàng)建類的實例
- 初始化一個類的子類
- 初始化一個父類的子類势决,也是對這個父類的主動使用
- Java虛擬機啟動時被標注為啟動的類
- 啟動程序的類,包含main方法的類
所有的java虛擬機實現(xiàn)必須在每個類或接口被java程序 首次主動使用 時才初始化他們,換句話說就是以上6種情況废赞,而且是第1次主動使用才會在初始化徽龟。其他情況,如被動使用唉地,或第二次主動使用据悔,都不會執(zhí)行類的初始化传透。
調(diào)用ClassLoader類的loadClass方法加載一個類,并不是對類的主動使用极颓,不會導(dǎo)致類的初始化朱盐。調(diào)用ClassLoader類的loadClass方法只是執(zhí)行了加載操作。
二 示例演示——增加理解
示例1
class Singleton{
private static Singleton singleton = new Singleton();
public static int counter1;
public static int counter2 = 0;
private Singleton(){
counter1 ++;
counter2 ++;
}
public static Singleton getInstance(){
return singleton;
}
}
public class JVMTest {
public static void main(String[] args){
Singleton singleton = Singleton.getInstance();
System.out.println("counter1 = " + singleton.counter1);
System.out.println("counter2 = " + singleton.counter2);
}
}
輸出結(jié)果
1
0
在類的連接--準備階段菠隆,singleton被賦默認值null兵琳,counter1和counter2被賦默認值0;初始化的時候骇径,先初始化singleton躯肌,創(chuàng)建實例,在Singleton 構(gòu)造方法中counter1++破衔,counter2++后清女,counter1和counter2變成了1,然后在再初始化counter1和counter2晰筛,counter1沒有被賦值嫡丙,所以還是1, counter2被賦值了0读第,所以counter2最終為0曙博。
如果將上面的部分代碼順序改一下:
public static int counter1;
public static int counter2 = 0;
private static Singleton singleton = new Singleton();
輸出結(jié)果
1
1
如果看懂了上面的代碼,這個不難理解怜瞒。
示例2
public class Test2 {
public static void main(String[] args){
System.out.println(FinalTest.x);
}
}
class FinalTest{
public final static int x = 1/3;
static{
System.out.println("FinalTest static block");
}
}
輸出結(jié)果
0
final修飾的靜態(tài)變量要注意:如果這個變量在編譯時就能確定它的值父泳,就不會導(dǎo)致類被初始化,例如
public static final int x = 1/3
public class Test2 {
public static void main(String[] args){
System.out.println(FinalTest.x);
}
}
class FinalTest{
public final static int x = new Random().nextInt(100);
static{
System.out.println("FinalTest static block");
}
}
輸出結(jié)果
FinalTest static block
75
如果這個變量要在運行時才能確定它的值盼砍,才會導(dǎo)致類被初始化尘吗,例如
public static final int x = new Random().nextInt(100)
示例3
public class Test3 {
static {
System.out.println("Test3 static block");
}
public static void main(String[] args){
System.out.println(Child.b);
}
}
class Parent{
static int a = 3;
static{
System.out.println("Parent static block");
}
}
class Child extends Parent{
static int b = 4;
static{
System.out.println("Child static block");
}
}
輸出結(jié)果?
Test3 static block
Parent static block
Child static block
4
Test3是程序入口類浇坐,最先被加載初始化睬捶;先加載初始化父類,再子類近刘。
示例4
public class Test4 {
static {
System.out.println("Test4 static block");
}
public static void main(String[] args){
Parent2 parent2;
System.out.println("-------------");
parent2 = new Parent2();
System.out.println(Parent2.a);
System.out.println(Child2.b);
}
}
class Parent2{
static int a = 3;
static{
System.out.println("Parent2 static block");
}
}
class Child2 extends Parent2{
static int b = 4;
static{
System.out.println("Child2 static block");
}
}
輸出結(jié)果
Test4 static block
-------------
Parent2 static block
3
Child2 static block
4
Child2擒贸、Parent2是由同一個類加載器加載的,所以Parent2初始化了, Child2初始化的時候就不會再去初始化父類了觉渴。
PS: 如果有兩個加載器:A類的加載器介劫,B類的加載器,AB不是父子關(guān)系案淋,即使A類的加載器已經(jīng)初始化了Child2類座韵,在B類的加載中還是可以再去初始化Child2類的。詳解請看后續(xù)的 Java類加載器 文章
為什么下面這一行代碼不去初始化Parent2呢?
...
Parent2 parent2;
...
因為這只是聲明了一個變量誉碴,并沒有主動使用類宦棺,所以不會初始化。
示例5
public class Test5 {
public static void main(String[] args){
System.out.println(Child3.a);
Child3.doSomething();
}
}
class Parent3{
static int a = 3;
static{
System.out.println("Parent3 static block");
}
static void doSomething(){
System.out.println("doSomething");
}
}
class Child3 extends Parent3{
static{
System.out.println("Child3 static block");
}
}
輸出結(jié)果黔帕?
Parent3 static block
3
doSomething
Child3.a代咸,Child3.doSomething() 為什么Child3沒有別初始化?
只有當訪問的靜態(tài)變量或靜態(tài)方法確實在當前類或當前接口中定義時成黄,才可以認為是對類和接口的主動使用呐芥。而Child3.a,Child3.doSomething()調(diào)用的靜態(tài)變量或靜態(tài)方法不是在Child2類中定義的奋岁,而是在父類中定義的思瘟,所以不會對Child2進行初始化。
示例6
class C{
static {
System.out.println("class C");
}
}
public class Test1 {
public static void main(String[] args) throws Exception{
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class<?> clazz = loader.loadClass("com.zhh.jvm.loadClass.C");//加載 C 類
System.out.println("------------");
clazz = Class.forName("com.zhh.jvm.loadClass.C");
}
}
輸出結(jié)果厦取?
------------
class C
loader.loadClass("com.zhh.jvm.loadClass.C");
調(diào)用ClassLoader類的loadClass方法加載一個類潮太,并不是對類的主動使用管搪,不會導(dǎo)致類的初始化虾攻。調(diào)用ClassLoader類的loadClass方法只是執(zhí)行了加載操作。clazz = Class.forName("com.zhh.jvm.loadClass.C");是反射創(chuàng)建類的實例更鲁,是類的主動使用霎箍,所以導(dǎo)致類被初始化。
以上內(nèi)容澡为,是本人學習筆記和工作中的總結(jié)漂坏,僅供大家參考,有誤的地方還請指正
轉(zhuǎn)載媒至、引用請標明出處
http://www.reibang.com/p/853701433b3a
本文出自zhh_happig的簡書博客顶别,謝謝