天天看點

初始化與清理用構造器確定初始化構造器與方法的重載構造器中調用構造器構造器初始化成員初始化清理:終結處理和垃圾回收垃圾回收器如何工作

初始化與清理

  • 用構造器確定初始化
  • 構造器與方法的重載
  • 構造器中調用構造器
  • 構造器初始化
  • 成員初始化
  • 清理:終結處理和垃圾回收
  • 垃圾回收器如何工作

用構造器確定初始化

在Java中,通過提供構造器,類的設計者可確定每個對象都會得到初始化。建立對象時,如果其類具有構造器,Java就會在使用者有能力操作對象之前自動調用相應的構造器,進而保證了初始化的進行。

構造器的命名現在有兩個問題:第一,所取的任何名字都可能與類的某個成員名稱相沖突,第二,調用構造器是編譯器的責任,是以必須讓編譯器知道應該調用哪個方法。C++語言中采用的解決方案看來最簡單且更符合邏輯,是以在Java中也采用了這種方案:即構造器采用與類相同的名稱。考慮到在初始化期間要自動調用構造器,這種做法就順理成章了。

在使用new關鍵字建立對象時,将會為對象配置設定存儲空間,并調用相應的構造器。這就確定了在你能操作對象之前,它已經被恰當地初始化了。

請注意,由于構造器的名稱必須與類名完全相同,是以“每個方法首字母小寫”的編碼風格并不适用于構造器。

不接受任何參數的構造器叫做預設構造器,Java文檔中通常使用術語無參構造器。但是和其他方法一樣,構造器也能帶有形式參數,以便指定如何建立對象。

public class Test {

	public Test() {
		System.out.println("調用了無參構造器");
	}
	
	public Test(String str){
		System.out.println("調用了有參構造器,參數是:"+str);
	}
	
	public static void main(String[] args) {
		new Test();
	}//輸出:調用了無參構造器
}
           

構造器是一種特殊類型的方法,因為它沒有傳回值。這與傳回值為空(void)明顯不同。對于空傳回值,盡管方法本身不會自動傳回什麼,但仍可選擇讓它傳回别的東西。構造器則不會傳回任何東西,你别無選擇(new表達式确實傳回了對建立對象的引用,但構造器本身并沒有任何傳回值)。假如構造器具有傳回值,并且允許人們自行選擇傳回類型,那麼勢必得讓編譯器知道該如何處理此傳回值。

構造器與方法的重載

在Java裡,構造器是強制重載方法名的另一個原因,既然構造器的名字已經由類名所決定,就隻能有一個構造器名,那麼要想用多種方式建立一個對象該怎麼辦呢?假設你要建立一個類,既可以用标準方式進行初始化,也可以從檔案裡讀取資訊來初始化,這就需要兩個構造器,一個預設認構造器,另一個取字元串作為形式參數一該字元串表示初始化對象所需的檔案名稱,由于都是構造器,是以它們必須有相同的名字,即類名,為了讓方法名相同而形式參數不同的構造器同時存在,必須用到方法重載。上面的例子展示了構造器的重載

盡管方法重裁是構造器所必需的,但它亦可應用于其他方法,且用法同樣友善,下面的例子将展示方法的重載。

public class Test {

	public static void printTest(){
		System.out.println("printTest()方法");
	}
	
	public static void printTest(String str){
		System.out.println("printTest(String str)方法,參數是:"+str);
	}
	
	public static void main(String[] args) {
		Test.printTest("1");
	}//輸出:printTest(String str)方法,參數是:1
}
           

注意

  1. 區分方法重載,每個重載的方法都必須有一個獨一無二的參數類型清單(數量的不同或參數類型的不同)。
  2. 涉及基本類型的重載,基本類型能從一個“較小”的類型自動提升至一個“較大”的類型。當參數為常數值5時,将被當作int值處理,是以如果有某個重載方法接受int型參數,它就會被調用。至于其他情況,如果傳入的資料類型(實際參數類型)小于方法中聲明的形式參數類型,實際資料類型就會被提升。char型略有不同,如果無法找到恰好接受char參數的方法,就會把char直接提升至int型。 如果傳入的實際參數較大,就得通過類型轉換來執行窄化轉換。如果不這樣做,編譯器就會報錯。
  3. 以傳回值區分重載方法是行不通的。

構造器中調用構造器

可能一個類寫了多個構造器,有時可能想在一個構造器中調用另一個構造器,以避免重複代碼。可用this關鍵字做到這一點。

通常寫this的時候,都是指“這個對象”或者“目前對象”,而且它本身表示對目前對象的引用。在構造器中,如果為this添加了參數清單,那麼就有了不同的含義。這将産生對符合此容數清單的某個構造器的明确調用;這樣,調用其他構造器就有了直接的途徑。

public class SuperTest {

	public SuperTest() {
		System.out.print("調用SuperTest()構造器,");
	}
}

public class Test extends SuperTest{
	
	public int s = 1;

	public Test(int s){
		this(s+"");
		System.out.print("調用Test(int i)構造器");
		System.out.println(this.s);
	}
	public Test(String str){
		super();
		System.out.print("調用Test(String str)構造器");
	}

	public static void main(String[] args) {
		new Test(1);
	}
	/**
	 * 輸出:
	 * 調用SuperTest()構造器
	 * 調用Test(String str)構造器
	 * 調用Test(int i)構造器
	 * 1
	 * */
}
           
  1. this關鍵字

    構造器Test(int i)表明:盡管可以用this調用一個構造器,但卻不能調用兩個。此外,必須将構造器調用置于最起始處,否則編譯器會報錯。這個例子也展示了this的一種用法。

    由于參數s的名稱和資料成員s的名字相同,是以會産生歧義。使用this來代表資料成員就能解決這個問題。在Java程式代碼中經常出現這種寫法。這是this的另一種用法。

    this關鍵字隻能在方法内部使用,表示對“調用方法的那個對象”的引用。this的用法和其他對象引用并無不同。但要注意,如果在方法内部調用同一個類的另一個方法,就不必使用this,直接調用即可。目前方法中的this引用會自動應用于同一類中的其他方法。

  2. static關鍵字

    了解this關健字之後,就能更全面地了解static(靜态)方法的含義。 static方法就是沒有static的方法。在static方法的内部不能調用非靜态方法,反過來倒是可以的。而且可以在沒有建立任何對象的前提下,僅僅通過類本身來調用static方法。這實際上正是static方法的主要用途。它很像全局方法。Java中禁止使用全局方法,但你在類中置入static方法就可以通路其他static方法和static域。

構造器初始化

可以用構造器來進行初始化。在運作時刻,可以調用方法或執行某些動作來确定初値,這為程式設計帶來了更大的靈活性。但要牢記:無法阻止自動初始化的進行,它将在構造器被調用之前發生。是以,假如使用下述代碼:

public class Counter{
	int i;
	public Counter(){
		i = 7;
	}
}
           

那麼i首先會被置0,然後變成7。對于所有基本類型和對象引用,包括在定義時已經指定初值的變量,這種情況都是成立的。是以,編譯器不會強制你一定要在構造器的某個地方或在使用它們之前對元素進行初始化——因為初始化早已得到了保證。

  1. 變量初始化順序

    在類的内部,變量定義的先後順序決定了初始化的順序。即使變量定文散布于方法定義之間,它們仍舊會在任何方法(包括構造器)被調用之前得到初始化。

  2. 靜态資料的初始化

    無論建立多少個對象,靜态資料都隻占用一份存儲區域。static關鍵字不能應用于局部變量,是以它隻能作用于域。如果一個域是靜态的基本類型域,且也沒有對它進行初始化,那麼它就會獲得基本類型的标準初值;如果它是一個對象引用,那麼它的預設初始化值就是null。

  3. 顯式的靜态初始化

    Java允許将多個靜态初始化動作組織成一個特殊的“靜态子句”(有時也叫做“靜态塊”)。 就像下面這樣:

public class Counter{
	int i;
	static{
		i = 7;
	}
}
           

盡管上面的代碼看起來像個方法,但它實際隻是一段跟在static關鍵字後面的代碼。與其他靜态初始化動作一樣,這段代碼僅執行一次:當首次生成這個類的一個對象時,或者首次通路屬于那個類的靜态資料成員時(即便從未生成過那個類的對象)。

  1. 非靜态執行個體初始化

    Java中也有被稱為執行個體初始化的類似文法,用來初始化每一個對象的非靜态變量。例如:

public class Test2 {

	public Test2() {
		System.out.println("調用Test2()構造器");
	}
}

public class Test {
	
	public Test(){
		System.out.println("調用Test()構造器");
	}
	
	Test2 t2;
	
	{
		t2 = new Test2();
	}
	
	public static void main(String[] args) {
		new Test();
	}
	/**
	 * 輸出:
	 * 調用Test2()構造器
	 * 調用Test()構造器
	 * */
}
           

看起來它與靜态初始化子句一模一樣,隻不過少了static關鍵字。這種話法對手支援“匿名内部類”的初始化是必須的,但是它也使得你可以保證無論調用了哪個顯式構造器,某些操作都會發生。從輸出中可以看到執行個體初始化子句是在構造器之前執行的。

  1. 對象的建立過程

    初始化的順序是先靜态對象(如果它們尚未因前面的對象建立過程而被初始化) ,而後是非靜态對象。總結一下對象的建立過程,假設有個名為Dog的類:

    1.即使沒有顯式地使用static關鍵字,構造器實際上也是靜态方法。是以,當首次建立類型為Dog的對象時(構造器可以看成靜态方法),或者Dog類的靜态方法/靜态域首次被通路時,Java解釋器必須査找類路徑,以定位Dog.class檔案。

    2.然後載入Dog.class檔案,有關靜态初始化的所有動作都會執行。是以,靜态初始化隻在Class對象首次加載的時候進行一次。

    3.當用new Dog()建立對象的時候,首先将在堆上為Dog對象配置設定足夠的存儲空間。

    4.這塊存儲空間會被清零,這就自動地将Dog對象中的所有基本類型資料都設定成了預設值(對數字來說就是0,對字元型也相同,布爾型為false),而引用則被設定成了null。

    5.執行所有出現于字段定義處的初始化動作。

    6.執行構造器。這可能會牽涉到很多動作,尤其是涉及繼承的時候。

成員初始化

Java盡力保證:所有變量在使用前都能得到恰當的初始化對于方法的局部變量Java以編譯時錯誤的形式來貫徹這種保證。是以如果寫成:

void f(){
	int i;
	i++;	//Error - i not initialized
}
           

就會得到一條錯誤,消息告訴你i可能未初始化。

  1. 指定初始值

    如果想為某個變量賦初值,該怎麼做呢?有一種很直接的方法就是在定義類成員變量的地方為其指派 。以下代碼片段修改:

void f(){
	int i = 1;
	i++;
}
           

也可以用同樣的方法初始化非基本類型的對象。

class Dog{}

public class Animal(){
	Dog d = new Dog();
}
           

清理:終結處理和垃圾回收

程式員都了解初始化的重要性,但常常會忘記同樣也重要的清理工作。畢竟,誰需要清理一個int呢?但在使用程式庫時,把一個對象用完後就“棄之不顧”的做法并非總是安全的。當然,Java有垃圾回收器負責回收無用對象占據的記憶體資源。但也有特殊情況:假定你的對象(并非使用new)獲得了一塊“特殊”的記憶體區域,由于垃圾回收器隻知道釋放那些經由new配置設定的記憶體,是以它不知道該如何釋放該對象的這塊“特殊”記憶體。為了應對這種情況,Java允許在類中定義一個名為finalize()的方法。它的工作原理“假定”是這樣的:一旦垃圾回收器準備好釋放對象占用的存儲空間,将首先調用其finalize()方法,并且在下一次垃圾回收動作發生時,才會真正回收對象占用的記憶體。是以要是你打算用finalize(),就能在垃圾回收時刻做一些重要的清理工作。

這裡有一個潛在的程式設計陷阱,因為有些程式員(特別是C++程式員)剛開始可能會誤把finalize()當作C++中的行構函數(c++中銷毀對象必須用到這個函數)。是以有必要明确區分一下:在C++中,對象一定會被銷毀(如果程式中沒有缺陷的話),而Java裡的對象卻并非總是被垃圾回收。或者換句話說:

1.對象可能不被垃圾回收

2.垃圾回收并不等于“折構”

牢記這些,就能遠離困擾。這意味着在你不再需要某個對象之前,如果必須執行某些動作,那麼你得自己去做。Java并未提供“析構函數”或相似的概念,要做類似的清理工作,就必須自己動手建立一個執行清理工作的普通方法。

也許你會發現, 隻要程式沒有瀕臨存儲空間用完的那一刻,對象占用的空間就總也得不到釋放。如果程式執行結束并且垃圾回收器一直都沒有釋放你建立的任何對象的存儲空間,則随着程式的退出,那些資源也會全部交還給作業系統。這個政策是恰當的,因為垃圾回收本身也有開銷,要是不使用它,那就不用支付這部分開銷了。

1.finalize()的用途何在

此時,讀者已經明白了不該将finalize()作為通用的清理方法。那麼,finalize()的真正用途是什麼呢?這引出了要記住的第三點:

3.垃圾回收隻與記憶體有關。

也就是說,使用垃圾回收器的唯一原因是為了回收程式不再使用的記憶體。是以對于垃圾回收有關的任何行為來說(尤其是finalize()方法),它們也必須同記憶體及其回收有關。

但這是否意味着要是對象中含有其他對象,finalize()就應該明确釋放那些對象呢?不,無論對象是如何建立的,垃圾回收器都會負責釋放對象占據的所有記憶體。這就将對finalize()的需求限制到一種特殊情況,即通過某種建立對象方式以外的方式為對象配置設定了存儲空間 。不過,我們也看到了,Java中一切皆為對象,那這種特殊情況是怎麼回事呢?

看來之是以要有finalize(),是由于在配置設定記憶體時可能采用了類似C語言中的做法,而非Java 中的通常做法。這種情況主要發生在使用“本地方法”的情況下,本地方法是一種在Java中調用非Java代碼的方式(關手本地方法的讨論見Java程式設計思想電子版第2版,在www.MindView.net網站上有收錄)。本地方法目前隻支援C和C++,但它們可以調用其它語言寫的代碼,是以實際上可以調用任何代碼。

至此,我們或許已經明白了不要過多地使用finalize()的道理了。對,它确實不是進行普通的清理工作的合适場所。那麼,普通的清理工作應該在哪裡執行呢?

2.你必須實施清理

要清理一個對象,使用者必須在需要清理的時刻調用執行清理動作的方法。這聽起來似乎很簡單。在Java中,垃圾同收器會幫助你釋放使用new建立對象的存儲空間。記住,無論是“垃圾回收”還是“終結”,都不保證一定會發生。如果Java虛拟機(JVM)并未面臨記憶體耗盡的情形,它是不會浪費時間去執行垃圾回收以恢複記憶體的。

3.終結條件

通常,不能指望finalize(),必須建立其他的“清理方法,并且明确地調用它們。看來,finalize()隻能存在于程式員很難用到的一些晦澀用法裡了。不過,finalize()還有一個有趣的用法,它并不依賴于每次都要對finalize()進行調用,這就是對象終結條件(這個術語是由Bill Venners和Bruce Eckel發明)的驗證。

當對某個對象不再感興趣——也就是它可以被清理了,這個對象應該處于某種狀态,使它占用的記憶體可以被安全地解放。例如,要是對象代表了一個打開的檔案,在對象被回收前程式員應該關閉這個檔案。隻要對象中存在沒有被适當清理的部分,程式就存在很隐海的缺陷。finalize()可以用來最終發現這種情況——盡管它并不總是會被調用。如果某次finalize()的動作使得缺陷被發現,那麼就可據此找出問題所在——這才是人們真正關心的。以下是個簡單的例子,示範了finalize()可能的使用方式:

class Book{
	boolean checkedOut = false;
	Book(boolean checkOut){
		checkedOut = checkOut;
	}
	
	void checkIn(){
		checkedOut = false;
	}

	protected void finalize(){
		if(checkedOut){
			System.out.print("Error:checked out");
			//super.finalize();
		}
	}
}

class Test{
	public static void main(String[] args){
		Book b = new Book(true);
		b.checkIn();
		new Book(true);
		System.gc();
	}
	//輸出:Error:checked out
}
           

本例的終結條件是:所有的Book對象在被當作垃圾回收前都應該被簽入(checkin)。但在main()方法中,由于程式員的錯誤,有一本書未被簽入。要是沒有finalize()來驗證終結條件,将很難發現這種缺陷 。

注意,System.gc()用于強制進行終結動作。即使不這麼做,通過重複地執行程式(假設程式将配置設定大量的存儲空間而導緻垃圾回收動作的執行),最終也能找出錯誤的Book對象。

你應該總是假設基類版本的finalize()也要做某些重要的事情,是以要使用super來調用它,就像在Book.finalize()中看到的那樣。在本例中它被注釋掉了。

垃圾回收器如何工作

在以前所用過的程式語言中,在堆上配置設定對象的代價十分高昂, 是以我們自然會覺得Java中所有對象(基本類型除外)都在堆上配置設定的方式也非常高昂。然而,垃圾回收器對于提高對象的建立速度,卻具有明顯的效果。聽起來很奇怪——存儲空間的釋放競然會影響存儲空間的配置設定,但這确實是某些Java虛拟機的工作方式。這也意味着,Java從堆配置設定空間的速度,可以和其他語言從堆棧上配置設定空間的速度相媲美。

在某些Java虛拟機中,堆地的實作,打個比方:它就像一個傳送帶,每配置設定一個新對象,它就往前移動一格。這意味着對象存儲空間的配置設定速度非常快。Java的“堆指針”隻是簡單地移動到尚未配置設定的區域,其效率比得上C++在堆棧上配置設定空同的效率。當然,實際過程中在簿記工作方面還有少量額外開銷,但比不上査找可用空間開銷大。

Java中的堆未必完全像傳送帶那樣工作。要真是那樣的話,勢必會導緻頻繁的記憶體頁面排程——将其移進移出硬碟。是以會顯得需要擁有比實際需要更多的記憶體。頁面排程會顯著地影響性能,最終,在建立了足夠多的對象之後,記憶體資源将耗盡。其中的秘密在于垃圾回收器的介入。當它工作時,将一面回收空間,一面使堆中的對象緊湊排列,這樣“堆指針”就可以很容易移動到更靠近傳送帶的開始處,也就盡量避免了頁面錯誤。通過垃圾回收器對對象重新排列,實作了一種高速的、有無限空間可供配置設定的堆模型。

要想更好地了解java中的垃圾回收,先了解其他系統中的拉圾回收機制将會很有幫助。引用記數是一種簡單但速度很慢的垃圾回收技術。每個對象都含有一個引用記數器,當有引用連接配接至對象時,引用計數加1。當引用離開作用域或被置為null時,引用計數減1。雖然管理引用記數的開銷不大,但這項開銷在整個程式生命周期中将持續發生。垃圾回收器會在含有全部對象的清單上周遊,當發現某個對象的引用計數為0時,就釋放其占用的空間(但是,引用記數模式經常會在記數值變為0時立即釋放對象)。這種方法有個缺陷,如果對象之間存在循壞引用,可能會出現“對象應該被回收,但引用計數卻不為零”的情況。對垃圾回收器而言,定位這樣的互動自引用的對象組所需的工作量極大。引用記數常用來說明垃圾收集的工作方式,但似乎從未被應用于任何一種Java虛拟機實作中。

在一些更快的模式中,垃圾回收器并非基于引用記數技術。它們依據的思想是:對任何“活”的對象,一定能最終追溯到其存活在堆棧或靜态存儲區之中的引用。這個引用鍊條可能會穿過數個對象層次。由此,如果從堆棧和靜态存儲區開始,周遊所有的引用,就能找到所有“活”的對象。對于發現的每個引用,必須追蹤它所引用的對象,然後是此對象包含的所有引用,如此反複進行,直到“根源與堆棧和靜态存儲區的引用”所形成的網絡全部被通路為止。你所通路過的對象必須都是“活”的。注意,這就解決了“互動自引用的對象組”的問題——這種現象根本不會被發現,是以也就自動回收了。

停止一複制(stop-and-copy)

在這種方式下,Java虛拟機将采用一種自适應的垃圾回收技術。至于如何處理找到的存活對象,取決于不同的Java虛拟機實作。有一種做法名為停止一複制(stop-and-copy)。顯然這意味着,先暫停程式的運作(是以它不屬于背景回收模式),然後将所有存活的對象從目前堆複制到另一個堆,沒有被複制的全部都是拉圾。當對象被複制到新堆時,它們是一個挨着一個的,是以新堆保持緊湊排列,然後就可以按前述方法簡單、直接地配置設定新空間了。

當把對象從一處搬到另一處時,所有指向它的那些引用都必須修正。位于堆或或靜态存儲區的引用可以直接被修正,但可能還有其他指向這些對象的引用,它們在周遊的過程中才能被找到(可以想像成有個表格,将舊位址映射至新位址)。

對于這種所謂的“複制式回收器”而言,效率會降低,這有兩個原因。首先,得有兩個堆,然後得在這兩個分離的堆之間來回搗騰,進而得維護比實際需要多一倍的空間。某些Java虛拟機對此問題的處理方式是,按需從堆中配置設定幾塊較大的記憶體,複制動作發生在這些大塊記憶體之間。

第二個問題在于複制。程式進入穩、定狀态之後,可能隻會産生少量垃扱,甚至沒有垃圾。 盡管如此,複制式回收器仍然會将所有記憶體自一處複制到另一處,這很浪費。為了避免這種情形,一些Java虛拟機會進行檢査:要是沒有新垃圾産生,就會轉換到另一種工作模式(即“自适應”)。這種模式稱為标記一清掃(mark-md-sweep),Sun公司早期版本的Java虛拟機使用了這種技術。對一般用途而言,“标記一清掃”方式速度相當慢,但是當你知道隻會産生少量垃圾甚至不會産生垃圾時,它的速度就很快了 。

标記一清掃(mark-md-sweep)

“标記一清掃所依據的思路同樣是從堆棧和靜态存儲區出發,周遊所有的引用,進而找出所有存活的對象。每當它找到一個存活對象,就會給對象設一個标記,這個過程中不會回收任何對象。隻有全部标記工作完成的時候,清理動作才會開始。在清理過程中,沒有标記的對象将被釋放,不會發生任何複制動作。是以剩下的堆空間是不連續的,垃圾回收器要是希望得到連續空間的話,就得重新整理剩下的對象。

“停止一複制”的意思是這種垃圾回收動作不是在背景進行的;相反,垃圾回收動作發生的同時,程式将會被暫停。在Sun公司的文檔中會發現,許多參考文獻将垃圾回收視為低優先級的背景程序,但事實上垃圾回收器在Sun公司早期版本的Java虛拟機中并非以這種方式實作的。當可用記憶體數量較低時,Sun版本的垃圾回收器會暫停運作程式,同樣,“标記一清掃”工作也必須在程式暫停的情況下才能運作。

自适應的垃圾回收機制

如前文所述,在這裡所讨論的Java虛拟機中,記憶體配置設定以較大的“塊”為単位。如果對象較大,它會占用單獨的塊。嚴格來說,“停止一複制”要求在釋放舊有對象之前,必須先把所有存活對象從舊堆複制到新堆,這将導緻大量記憶體複制行為。有了塊之後,垃圾回收器在回收的時候就可以往廢棄的塊裡拷貝對象了。每個塊都用相應的代數(generation count)來記錄它是否還存活。通常,如果塊在某處被引用,其代數會增加;垃圾回收器将對上次回收動作之後新配置設定的塊進行整理。這對處理大量短命的臨時對象很有幫助。垃圾回收器會定期進行完整的清理動作——大型對象仍然不會被複制(隻是其代數會增加),内含小型對象的那些塊則被複制并整理。Java虛拟機會進行監視,如果所有對象都很穩定,垃圾回收器的效率降低的話,就切換到“标記一清掃”方式;同樣,Java虛拟機會跟蹤“标記一清掃”的效果,要是堆空間出現很多碎片,就會切換回“停止一複制”方式。這就是“自适應”技術,你可以給它個啰嗦的稱呼:“自适應的、分代的、停止一複制、标記一精掃”式垃圾回收器。

即時編譯技術(Just-In-Time,JIT)

Java虛拟機中有許多附加技術用以提升速度。尤其是與加載器操作有關的,被稱為“即時”(J,-n-Tirne, JIT)編譯器的技術。這種技術可以把程式全部或部分翻譯成本地機器碼(這本來是Java虛拟機的工作),程式運作速度是以得以提升。當需要裝載某個類(通常是在為該類建立第一個對象)時,編譯器會先找到其.class檔案,然後将該類的位元組碼裝入記憶體。此時,有兩種方案可供選擇。一種是就讓即時編譯器編譯所有代碼。但這種做法有兩個缺陷:這種加載動作散落在整個程式生命周期内,累加起來要花更多時問;并且會增加可執行代碼的長度(位元組碼要比即時編譯器展開後的本地機器碼小很多),這将導緻頁面排程,進而降低程式速度。另一種做法稱為惰性評估(lazy evaluation),意思是及時編譯器隻在必要的時候才編譯代碼。這樣,從不會被執行的代碼也許就壓根不會被JIT所編譯。新版JDK中的Java HotSpot技術就采用了類似方法,代碼每次被執行的時候都會做一些優化,是以執行的次數越多,它的速度就越快。

  1. 本文來源《Java程式設計思想(第四版)》