1. 程式人生 > >【java虛擬機器系列】java中類與物件的載入順序

【java虛擬機器系列】java中類與物件的載入順序

首先了解一下Java虛擬機器初始化的原理。

JVM通過加裝、連線和初始化一個Java型別,使該型別可以被正在執行的Java程式所使用。型別的生命週期如下圖所示:


裝載和連線必須在初始化之前就要完成。


類初始化階段,主要是為類變數賦予正確的初始值。這裡的“正確”初始值指的是程式設計師希望這個類變數所具備的起始值。一個正確的初始值是通過類變數初始化語句或者靜態初始化語句給出的。初始化一個類包含兩個步驟:


1) 如果類存在直接超類的話,且直接超類還沒有被初始化,就先初始化直接超類。


2) 如果類存在一個類初始化方法,就執行此方法。


那什麼時候類會進行初始化呢?Java 虛擬機器規範為類的初始化時機做了嚴格定義:在首次主動使用時初始化。


那哪些情形才符合首次主動使用的標準呢?Java虛擬機器規範對此作出了說明,他們分別是:


1) 建立類的新例項;


2) 呼叫類的靜態方法;


3) 操作類或介面的靜態欄位(final欄位除外);


4) 呼叫Java的特定的反射方法;


5) 初始化一個類的子類;


6) 指定一個類作為Java虛擬機器啟動時的初始化類。


除了以上六種情形以外,所有其它的方式都是被動使用的,不會導致類的初始化。


一旦一個類被裝載、連線和初始化,它就隨時可以使用了。現在我們來關注物件的例項化,物件例項化和初始化是就是物件生命的起始階段的活動。


Java編譯器為它編譯的每個類都至少生成一個例項初始化方法,即<init>()方法。原始碼中的每一個類的構造方法都有一個相對應的<init>()方法。如果類沒有明確地宣告任何構造方法,編譯器則為該類生成一個預設的無參構造方法,這個預設的構造器僅僅呼叫父類的無參構造器。


一個<init>()方法內包括的程式碼內容可能有三種:呼叫另一個<init>() 方法;對例項變數初始化;構造方法體的程式碼。
如果構造方法是明確地從呼叫同一個類中的另一個構造方法開始,那它對應的 <init>() 方法體內包括的內容為:


一個對本類的<init>()方法的呼叫;
實現了對應構造方法的方法體的位元組碼。
如果構造方法不是通過呼叫自身類的其它構造方法開始,並且該物件不是 Object 物件,那 <init>() 法內則包括的內容為:

一個父類的<init>()方法的呼叫;
任意例項變數初始化方法的位元組碼;
 實現了對應構造方法的方法體的位元組碼。

好,那我們再以程式碼的形式來具體分析一下:

SuperInitField p = new SuperInitField();  
//SuperInitField的超類是Object  
//建立SuperInitField物件,屬於首次主動使用,因此要先初始化Object類,然後再呼叫SuperInitField類變數初始化語句或者靜態初始化語句,所以要輸出static parent  
//類被裝載、連線和初始化之後,建立一個物件,因此需要首先呼叫了Object的預設構造方法,然後再呼叫自己的構造方法,所以要輸出parent  
  
SuperInitField c = new SubInitField();  
//SubInitField繼承自SuperInitField  
//建立SubInitField物件,屬於首次主動使用,父類SuperInitField已被初始化,因此只要呼叫SubInitField類變數初始化語句或者靜態初始化語句,所以要輸出static child  
//類被裝載、連線和初始化之後,建立一個物件,因此需要首先呼叫了SuperInitField的構造方法,然後再呼叫自己的構造方法,所以要輸出parent,然後再輸出child 

在來詳細講講java中的static程式碼塊

static表示“全域性”或者“靜態”的意思,用來修飾成員變數和成員方法,也可以形成靜態static程式碼塊,但是Java語言中沒有全域性變數的概念。被static修飾的成員變數和成員方法獨立於該類的任何物件。也就是說,它不依賴類特定的例項,被類的所有例項共享。


  只要這個類被載入,Java虛擬機器就能根據類名在執行時資料區的方法區內定找到他們。因此,static物件可以在它的任何物件建立之前訪問,無需引用任何物件。用public修飾的static成員變數和成員方法本質是全域性變數和全域性方法,當宣告它類的物件市,不生成static變數的副本,而是類的所有例項共享同一個static變數。


  static變數前可以有private修飾,表示這個變數可以在類的靜態程式碼塊中,或者類的其他靜態成員方法中使用(當然也可以在非靜態成員方法中使用--廢話),但是不能在其他類中通過類名來直接引用,這一點很重要。實際上你需要搞明白,private是訪問許可權限定,static表示不要例項化就可以使用,這樣就容易理解多了。static前面加上其它訪問許可權關鍵字的效果也以此類推。


  static修飾的成員變數和成員方法習慣上稱為靜態變數和靜態方法,可以直接通過類名來訪問,訪問語法為:
  類名.靜態方法名(引數列表...)
  類名.靜態變數名


  用static修飾的程式碼塊表示靜態程式碼塊,當Java虛擬機器(JVM)載入類時,就會執行該程式碼塊(用處非常大,呵呵)。 


  1、static變數
    按照是否靜態的對類成員變數進行分類可分兩種:一種是被static修飾的變數,叫靜態變數或類變數;另一種是沒有被static修飾的變數,叫例項變數。


  兩者的區別是:
   對於靜態變數在記憶體中只有一個拷貝(節省記憶體),JVM只為靜態分配一次記憶體,在載入類的過程中完成靜態變數的記憶體分配,可用類名直接訪問(方便),當然也可以通過物件來訪問(但是這是不推薦的)。
   對於例項變數,沒建立一個例項,就會為例項變數分配一次記憶體,例項變數可以在記憶體中有多個拷貝,互不影響(靈活)。


  所以一般在需要實現以下兩個功能時使用靜態變數:
    在物件之間共享值時
    方便訪問變數時




  2、靜態方法
    靜態方法可以直接通過類名呼叫,任何的例項也都可以呼叫,
    因此靜態方法中不能用this和super關鍵字,不能直接訪問所屬類的例項變數和例項方法(就是不帶static的成員變數和成員成員方法),只能訪問所屬類的靜態成員變數和    成員方法。
    因為例項成員與特定的物件關聯!這個需要去理解,想明白其中的道理,不是記憶!!!
    因為static方法獨立於任何例項,因此static方法必須被實現,而不能是抽象的abstract。


    例如為了方便方法的呼叫,Java API中的Math類中所有的方法都是靜態的,而一般類內部的static方法也是方便其它類對該方法的呼叫。


    靜態方法是類內部的一類特殊方法,只有在需要時才將對應的方法宣告成靜態的,一個類內部的方法一般都是非靜態的


  3、static程式碼塊


   static程式碼塊也叫靜態程式碼塊,是在類中獨立於類成員的static語句塊,可以有多個,位置可以隨便放,它不在任何的方法體內,JVM載入類時會執行這些靜態的程式碼塊,如果static程式碼塊有多個,JVM將按照它們在類中出現的先後順序依次執行它們,每個程式碼塊只會被執行一次。例如:

public class Test{
private static int a;
private int b;

static{
Test.a=3;
System.out.println(a);
Test t=new Test();
t.f();
t.b=1000;
System.out.println(t.b);
}
static{
Test.a=4;
System.out.println(a);
}
public static void main(String[] args) {
// TODO 自動生成方法存根
}
static{
Test.a=5;
System.out.println(a);
}
public void f(){
System.out.println("hhahhahah");
}
} 
執行結果:

3
hhahhahah
1000
4
5

  4、static和final一塊用表示什麼
  static final用來修飾成員變數和成員方法,可簡單理解為“全域性常量”!
  對於變數,表示一旦給值就不可修改,並且通過類名可以訪問。
  對於方法,表示不可覆蓋,並且可以通過類名直接訪問。


  有時你希望定義一個類成員,使它的使用完全獨立於該類的任何物件。通常情況下,類成員必須通過它的類的物件訪問,但是可以建立這樣一個成員,它能夠被它自己使用,而不必引用特定的例項。在成員的宣告前面加上關鍵字static(靜態的)就能建立這樣的成員。如果一個成員被宣告為static,它就能夠在它的類的任何物件建立之前被訪問,而不必引用任何物件。你可以將方法和變數都宣告為static。static 成員的最常見的例子是main( ) 。因為在程式開始執行時必須呼叫main() ,所以它被宣告為static。


  宣告為static的變數實質上就是全域性變數。當宣告一個物件時,並不產生static變數的拷貝,而是該類所有的例項變數共用同一個static變數。宣告為static的方法有以下幾條限制:
  •它們僅能呼叫其他的static 方法。
  •它們只能訪問static資料。
  •它們不能以任何方式引用this 或super(關鍵字super 與繼承有關,在下一章中描述)。
  如果你需要通過計算來初始化你的static變數,你可以宣告一個static塊,Static 塊僅在該類被載入時執行一次。下面的例子顯示的類有一個static方法,一些static變數,以及一個static 初始化塊:

class UseStatic {
static int a = 3;
static int b;

static void meth(int x) {
System.out.println("x = " + x);
System.out.println("a = " + a);
System.out.println("b = " + b);
}

static {
System.out.println("Static block initialized.");
b = a * 4;
}

public static void main(String args[]) {
meth(42);
}
} 
一旦UseStatic 類被裝載,所有的static語句被執行。首先,a被設定為3,接著static 塊執行(列印一條訊息),最後,b被初始化為a*4 或12。然後呼叫main(),main() 呼叫meth() ,把值42傳遞給x。3個println ( ) 語句引用兩個static變數a和b,以及區域性變數x 。
  注意:在一個static 方法中引用任何例項變數都是非法的。

  下面是該程式的輸出:

Static block initialized.
x = 42
a = 3
b = 12 
  在定義它們的類的外面,static 方法和變數能獨立於任何物件而被使用。這樣,你只要在類的名字後面加點號運算子即可。例如,如果你希望從類外面呼叫一個static方法,你可以使用下面通用的格式:
  classname.method( )
  這裡,classname 是類的名字,在該類中定義static方法。可以看到,這種格式與通過物件引用變數呼叫非static方法的格式類似。一個static變數可以以同樣的格式來訪問——類名加點號運算子。這就是Java 如何實現全域性功能和全域性變數的一個控制版本。

java 靜態程式碼塊 靜態方法區別
一般情況下,如果有些程式碼必須在專案啟動的時候就執行的時候,需要使用靜態程式碼塊,這種程式碼是主動執行的;需要在專案啟動的時候就初始化,在不建立物件的情況下,其他程式來呼叫的時候,需要使用靜態方法,這種程式碼是被動執行的. 靜態方法在類載入的時候 就已經載入 可以用類名直接呼叫
比如main方法就必須是靜態的 這是程式入口
兩者的區別就是:靜態程式碼塊是自動執行的;
靜態方法是被呼叫的時候才執行的.


靜態方法
(1)在Java裡,可以定義一個不需要建立物件的方法,這種方法就是靜態方法。要實現這樣的效果,只需要在類中定義的方法前加上static關鍵字。例如:


public static int maximum(int n1,int n2)


使用類的靜態方法時,注意:


a在靜態方法裡只能直接呼叫同類中其他的靜態成員(包括變數和方法),而不能直接訪問類中的非靜態成員。這是因為,對於非靜態的方法和變數,需要先建立類的例項物件後才可使用,而靜態方法在使用前不用建立任何物件。


b 靜態方法不能以任何方式引用this和super關鍵字,因為靜態方法在使用前不用建立任何例項物件,當靜態方法呼叫時,this所引用的物件根本沒有產生。


(2)靜態變數是屬於整個類的變數而不是屬於某個物件的。注意不能把任何方法體內的變數宣告為靜態,例如:
fun()
{
static int i=0;//非法。
}


(3)一個類可以使用不包含在任何方法體中的靜態程式碼塊,當類被載入時,靜態程式碼塊被執行,且只被執行一次,靜態塊常用來執行類屬性的初始化。例如:
static
{
}

靜態程式碼塊的初始化順序


class Parent{ 
static String name = "hello"; 

System.out.println("parent block"); 

static { 
System.out.println("parent static block"); 

public Parent(){ 
System.out.println("parent constructor"); 




class Child extends Parent{ 
static String childName = "hello"; 

System.out.println("child block"); 

static { 
System.out.println("child static block"); 

public Child(){ 
System.out.println("child constructor"); 




public class StaticIniBlockOrderTest { 


public static void main(String[] args) { 
new Child();//語句(*) 

}
 


 parent static block
child static block
parent block
parent constructor
child block
child constructor
  


分析:當執行new Child()時,它首先去看父類裡面有沒有靜態程式碼塊,如果有,它先去執行父類裡面靜態程式碼塊裡面的內容,當父類的靜態程式碼塊裡面的內容執行完畢之後,接著去執行子類(自己這個類)裡面的靜態程式碼塊,當子類的靜態程式碼塊執行完畢之後,它接著又去看父類有沒有非靜態程式碼塊,如果有就執行父類的非靜態程式碼塊,父類的非靜態程式碼塊執行完畢,接著執行父類的構造方法;父類的構造方法執行完畢之後,它接著去看子類有沒有非靜態程式碼塊,如果有就執行子類的非靜態程式碼塊。子類的非靜態程式碼塊執行完畢再去執行子類的構造方法,這個就是一個物件的初始化順序。


總結:
物件的初始化順序:首先執行父類靜態的內容,父類靜態的內容執行完畢後,接著去執行子類的靜態的內容,當子類的靜態內容執行完畢之後,再去看父類有沒有非靜態程式碼塊,如果有就執行父類的非靜態程式碼塊,父類的非靜態程式碼塊執行完畢,接著執行父類的構造方法;父類的構造方法執行完畢之後,它接著去看子類有沒有非靜態程式碼塊,如果有就執行子類的非靜態程式碼塊。子類的非靜態程式碼塊執行完畢再去執行子類的構造方法。總之一句話,靜態程式碼塊內容先執行,接著執行父類非靜態程式碼塊和構造方法,然後執行子類非靜態程式碼塊和構造方法。


注意:子類的構造方法,不管這個構造方法帶不帶引數,預設的它都會先去尋找父類的不帶引數的構造方法。如果父類沒有不帶引數的構造方法,那麼子類必須用supper關鍵子來呼叫父類帶引數的構造方法,否則編譯不能通過。


相關推薦

java虛擬機器系列java物件載入順序

首先了解一下Java虛擬機器初始化的原理。JVM通過加裝、連線和初始化一個Java型別,使該型別可以被正在執行的Java程式所使用。型別的生命週期如下圖所示: 裝載和連線必須在初始化之前就要完成。 類初始化階段,主要是為類變數賦予正確的初始值。這裡的“正確”初始值指的是程

深入理解 Java 虛擬機器筆記虛擬機器位元組碼執行引擎

7.虛擬機器位元組碼執行引擎 執行引擎是 Java 虛擬機器最核心的組成部分之一。在 Java 虛擬機器規範中制定了虛擬機器位元組碼執行引擎的概念模型,這個概念模型成為各種虛擬機器執行引擎的統一外觀(Facade)。不同的虛擬機器實現,執行引擎可能會有解釋執行和編譯執行兩種,有可能兩

深入理解 Java 虛擬機器筆記虛擬機器效能監控故障處理工具

3.虛擬機器效能監控與故障處理工具 定位問題時,知識和經驗是關鍵基礎、資料(執行日誌、異常堆疊、GC日誌、執行緒快照、堆轉儲快照)是依據、工具是運用知識處理資料的手段。 思維導圖 JDK的命令列工具 jps: 虛擬機器程序狀況工具 jps(JVM Proce

深入理解 Java 虛擬機器筆記檔案結構

5.類檔案結構 由於最近十年內虛擬機器以及大量建立在虛擬機器之上的程式語言如雨後春筍般出現並蓬勃發展,將我們編寫的程式編譯成二進位制本地機器碼(Native Code)已不再是唯一的選擇,越來越多的程式語言選擇了作業系統和機器指令集無關的、平臺中立的格式作為程式

Java虛擬機器探究5.常用JVM配置引數-棧的分配引數

在使用JVM編譯java時,都會去設定相關的引數(例如使用eclipse的時候,可以設定eclipse的eclipse.ini檔案來對jvm做一些引數配置)。jvm的引數設定主要涉及到三種,分別是Trace跟蹤引數、堆的分配引數、棧的分配引數。本章主要講解棧的分配引數的相關資

Java學習筆記系列Java8陣列(引用型別)、String、List、Set之間的相互轉換問題

陣列、String、List、Set之間的相互轉換問題 本博主要是相理清楚一些轉換的邏輯,順便總結一下貼出來。這裡是把一些可以JDK自帶的使用方法寫出了。不代表沒有其他的轉換方式。 前提宣告: 只要跟集合交流的陣列都是引用型別的陣列。因為集合本身不

Java虛擬機器學習記憶體區域

根據《Java虛擬機器規範(Java SE 7)》的規定,Java虛擬機器所管理的記憶體包括如圖所示的幾個執行時資料區域: JVM有兩種機制:一個是裝載具有合適名稱的類(類或是介面),包含類的裝載、連線、初始化的過程叫做類裝載子系統;另外的一個負責執行包含在已裝載的

Java虛擬機器系列001

作為當今排名靠前的Java程式語言,要想學好Java語言,必須知其然知其所以然,對Java的語法,特徵的掌握是基礎,其次,對於Java是如何運作,物件如何建立的,GC是如何回收物件的,也必須有一個清楚的瞭解,這樣才可以更好的瞭解JAVA是如何由編寫,到編譯,到執行的,我將整理Java系列,讓大家

阿裏面試系列Java線程的應用及挑戰

thread線程 ram 時間 title extend 歸類 ace 定義 code 文章簡介 上一篇文章【「阿裏面試系列」搞懂並發編程,輕松應對80%的面試場景】我們了解了進程和線程的發展歷史、線程的生命周期、線程的優勢和使用場景,這一篇,我們從Java層面更進一步了解

Java虛擬機器系列005

JVM類載入 本章節講解Class檔案中的資訊進入到虛擬機器後發生什麼變化 虛擬機器把描述的類的資料從Class檔案載入到記憶體,並對資料進行效驗,轉換解析和初始化,最終形成可以被虛擬機器直接使用的Java型別,這就是Java虛擬機器的類載入機制。 1.類載入的時機

深入理解java虛擬機器系列(一):java記憶體區域記憶體溢位異常

文章主要是閱讀《深入理解java虛擬機器:JVM高階特性與最佳實踐》第二章:Java記憶體區域與記憶體溢位異常 的一些筆記以及概括。 好了開始。如果有什麼錯誤或者遺漏,歡迎指出。 一、概述 先上一張圖 這張圖主要列出了Java虛擬機器管理的記憶體的幾個區域。 常有人

Java虛擬機器(JVM)的記憶體設定詳解

在一些規模稍大的應用中,Java虛擬機器(JVM)的記憶體設定尤為重要,想在專案中取得好的效率,GC(垃圾回收)的設定是第一步。 PermGen space:全稱是Permanent Generation space.就是說是永久儲存的區域,用於存放Class和Meta資

深入理解Java虛擬機器系列——JVM的GC理論詳解

GC的概念    GC:Garbage Collection 垃圾收集。這裡所謂的垃圾指的是在系統執行過程當中所產生的一些無用的物件,這些物件佔據著一定的記憶體空間,如果長期不被釋放,可能導致OOM(堆溢位)。記憶體區域中的程式計數器、虛擬機器棧、本地方法棧這3個區域隨著執行

BAT面試題系列Java面試必考題JVM的最完整詳解,深度解析背後原理

JVM是BAT面試中的Java必考題目。   想要完美解答JVM相關

深入理解java虛擬機器系列初篇(一):為什麼要學習JVM?

前言 本來想著關於寫JVM這個專欄,直接寫知識點乾貨的,但是想著還是有必要開篇講一下為什麼要學習JVM,這樣的話讓一些學習者心裡有點底的感覺比較好... 原因一:面試 不得不說,隨著網際網路門檻越來越高,JVM知識也是中高階程式設計師階段必問的一個話題!現在不像以前了,以前會點html都好找工作,現在由於學習

數據庫系列MySql的select的鎖表範圍

nbsp 範圍 nod 指定 lock 無數據 才會 rdb sele 由於InnoDB預設的是Row-Level Lock,只有明確指定主鍵的時候MySql才會執行Row lock,否則MySql將會執行Table Lock. 1、明確指定主鍵則是行鎖 2、明確指定主鍵,

Java虛擬系列Java內存結構簡介

內存空間 指示器 私有 以及 並且 內存區域 在服務器 規範 基礎 本文我們將講解Java虛擬機中各個區域以及各個區域的作用。 一.程序計數器什麽是程序計數器,有什麽作用?程序技術器是一塊比較小的內存區域,主要當做是線程中所執行的字節碼的行號指示器,字節碼解釋器工作時就是通

深入理解Java虛擬機器總結一垃圾收集器記憶體分配策略(二)

深入理解Java虛擬機器總結一垃圾收集器與記憶體分配策略(二) 垃圾回收概述 如何判定物件為垃圾物件 垃圾回收演算法 垃圾收集器詳解 記憶體分配策略 垃圾回收概述 如何判定物件為垃圾物件 引用計數法: 在物件

深入Java虛擬機器閱讀感-Java記憶體各個區域的描述(一)

Java虛擬機器管理的記憶體資料模型如下: 1.程式計數器             程式計數器(Program counter Register)是程式執行位元組碼的行號指示器,每個執行緒都有獨立的程式計數器,當執行其他執

《深度拆解Java虛擬機器》之Java虛擬機器是如何載入Java的?

一、JVM的類載入 Java 虛擬機器中的類載入,從 class 位元組碼檔案到記憶體中的類,按先後順序需要經過載入、連結以及初始化三大步驟。其中,連結過程中同樣需要驗證;而記憶體中的類沒有經過初始化,同樣不能使用。那麼,是否所有的 Java 類都