JVM類載入器
阿新 • • 發佈:2018-12-10
(1)下面第一種和第二種會初始化A執行它的static裡面的程式碼塊,但是第三種不會,主要原因就在於第三種情況訪問的A的靜態變數是靜態常量,所以雖然是主動呼叫了A,但是不會去初始化A,這算是靜態常量的特殊性。JVM01是入口類,所以它的靜態程式碼塊是肯定要執行的。
public class JVM01 {
static {
System.out.println("static main block");
}
public static void main(String[] args){
System.out.println(A.x);
}
}
class A {
// static final int x = new Random().nextInt(100);
// static int x = 10;
static final int x = 10;
static {
System.out.println("static A block");
}
}
(2)一般情況下,主動呼叫子類,會先初始化父類;反之主動呼叫父類的話,不會初始化子類,否則主動呼叫Object的話,所有的類都要初始化了。當然,如果父類已經被主動呼叫並初始化過了,再主動呼叫子類,就不會再去初始化父類了,前提是在同一個類載入器中。
public class JVM01 {
static {
System.out.println("static main block" );
}
public static void main(String[] args){
System.out.println(Child.y);
}
}
class Parent {
static int x = 10;
static {
System.out.println("static parent block");
}
}
class Child extends Parent{
static int y = 20;
static {
System.out.println("static child block" );
}
}
結果是:
static main block
static parent block
static child block
20
(3)前面說主動呼叫子類會先初始化父類,這個主動呼叫時有條件的,必須呼叫的是子類自己的靜態變數或靜態方法,意思是說必須在自己的類裡面定義的,如果是父類裡面定義的,雖然能呼叫,但因為不是在自己類裡面定義,所以呼叫時不能算是主動呼叫,所以不會初始化子類,而是直接初始化這個靜態變數或靜態方法定義所在的父類。
public class JVM01 {
static {
System.out.println("static main block");
}
public static void main(String[] args){
System.out.println(Child.x);
Child.doSomething();
}
}
class Parent {
static int x = 10;
static {
System.out.println("static parent block");
}
static void doSomething(){
System.out.println("do something");
}
}
class Child extends Parent{
static int y = 20;
static {
System.out.println("static child block");
}
}
結果是:
static main block
static parent block
10
do something
(4)哪些算是主動呼叫?
- 建立類的例項,即
new
一個,如果只是Parent parent;
還不算,需要parent = new Parent();
時才算是建立。 - 訪問某個類的靜態變數(除final常量)或靜態方法,或者對靜態變數賦值。
- 反射,就是`Class.forName(“xxx.xxx”)。
- 初始化它的子類,如果它沒被初始化過,也算是主動呼叫了它進行初始化。
- 啟動類。
(5)自然的,其他情況不算是主動呼叫,不會初始化。比如使用ClassLoader
去載入類的話,就不算主動呼叫,也就不會初始化。
public class JVM01 {
static {
System.out.println("static main block");
}
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class<?> clazz = classLoader.loadClass("Parent");
System.out.println("=============");
clazz = Class.forName("Parent");
}
}
class Parent {
static int x = 10;
static {
System.out.println("static parent block");
}
}
結果是:
static main block
=============
static parent block
(6)類載入器工作流程
- 類的載入。把.class檔案中的為禁止資料讀取到記憶體中,放在記憶體的方法區,並且在堆區建立一個相應的java.lang.Class物件,用來封裝類在方法區裡的資料結構。
- 類的連線。連線主要有3個步驟,一個是驗證,就是檢查是否滿足一些java標準或者檢查引用之間的正確性等;二是準備,主要是為靜態變數分配記憶體,並且初始化預設值;三是解析,主要是把符號引用轉化成直接引用,也就是說如果在一個類裡面有另一個類方法的引用,那麼就會把這行程式碼直接替換成一個指標,這個指標指向這另一個類方法在記憶體中的地址,這就是轉成直接引用。
- 類的初始化。這就是根據程式碼中的定義來給靜態變數賦值。
也就是說,我們的靜態變數有可能要經過兩次賦值,第一次是賦預設值,第二次是賦值我們寫的值。賦值我們自己寫的值也是依次從上往下來執行標記有static
的程式碼。
public class JVM01 {
public static void main(String[] args) throws ClassNotFoundException {
Parent parent = Parent.getInstance();
System.out.println(Parent.x);
System.out.println(Parent.y);
}
}
class Parent {
static Parent singleton = new Parent();
static int x = 10;
static int y;
private Parent(){
x++;
y++;
}
public static Parent getInstance(){
return singleton;
}
}
上面程式碼,當呼叫getInstance
的時候就算是主動呼叫了,所以開始初始化,初始化有順序,所以先執行第一行,也就是一個建構函式,x
和y
都賦值為1,然後執行第二行和第三行,把x
重新賦值了10,y不變,所以最終結果是:
10
1
如果靜態程式碼順序換一下,如下:
static int x = 10;
static int y;
static Parent singleton = new Parent();
那麼結果就是:
11
1