天天看點

《Java學習指南》—— 1.2 虛拟機

本節書摘來異步社群《java學習指南》一書中的第1章,第1.2節,作者:【美】patrick niemeyer , daniel leuck,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

java既是一種編譯語言也是一種解釋語言。java源代碼将被轉換為簡單的二進制指令,這與通常的微處理器機器碼非常類似。不過,c或c++ 源代碼要針對特定處理器模型而優化為本地指令,而java源代碼卻均會編譯為一種通用格式,即面向虛拟機的指令。

已編譯java位元組碼由一個java運作時解釋器執行。運作時系統可以完成一個實際處理器所做的所有正常操作,但是會在一個安全的虛拟環境中完成這些工作。它會執行一個基于棧的指令集,并像作業系統一樣管理記憶體。運作時系統将建立并操作基本(primitive)資料類型,另外加載和調用新近引用的代碼塊。最重要的是,所有這些都是遵循一個嚴格定義的開放規範完成的,任何人如若希望生成一個與java相容的虛拟機,都可實施此規範。綜合來看,虛拟機和語言定義則提供了一個完整的規範。這樣,基本java語言的所有特性都得到了義,而且與實作無關。例如,java指定了所有基本資料類型的大小,而不是将其留待平台實作來确定。

java解釋器是相對輕量級的,而且規模較小,它可以實作為某種特定平台所需的任何一種形式。解釋器可以作為一個單獨的應用運作,也可以嵌入到另一個軟體中(如web浏覽器)。綜合起來看,這意味着java代碼隐含的就是可移植的。同樣的java應用程式的位元組碼,可以運作在提供了java運作時環境的任何平台上,如圖1-1所示。你不需要為了不同的平台而提供應用程式的其他版本,并且你不必把源代碼分發給使用者。

《Java學習指南》—— 1.2 虛拟機

java代碼的基本機關是類。與其他面向對象語言類似,類是包含可執行代碼和資料的應用元件。已編譯的java類采用一種通用的二進制格式釋出,其中包含有java位元組碼和其他類資訊。類可以分散地加以維護,并儲存于本地的檔案或歸檔檔案中,也可以儲存在網絡伺服器上。應用需要類時,将在運作時動态地進行定位和加載。

除了特定于平台的運作時系統外,java還有大量的基本類,其中包含有依賴于架構的方法。這些本地方法(native method)相當于java虛拟機和真實世界之間的網關。它們在主機平台上以一種本地編譯語言實作,并提供對網絡、視窗系統和主機檔案系統等資源的低層級通路。然而,java的主要部分則完全用java編寫的,都是從這些基本部分演化而來,是以是可移植的。這包括基本java工具,如java編譯器、網絡和gui庫,它們都采用java編寫,是以在所有java平台上均可以下同的方式使用而不需要移植。

基于曆史的原因,往往認為解釋器很慢,但是由于java并非傳統的解釋語言。除了将源代碼編譯為可移植的位元組碼,java還得到了精心的設計,通過将位元組碼動态編譯為本地機器碼,使得運作時系統的軟體實作可以對其性能進行優化。這也稱為即時(just-in-time,jit)編譯或動态編譯。利用jit編譯,java代碼可以與本地已編譯代碼執行得一樣快,同時還可保持其可傳輸性和安全性。

對于力圖比較語言性能的人來說,這往往是一個誤區。已編譯java代碼隻是由于安全性和虛拟機設計(數組越界檢查)而導緻運作時性能有所下降,而這也是性能下降的唯一的内在因素。而其他的所有内容都可以優化為本地碼,這一點與靜态編譯語言所能做的并無二緻。除此之外,較之于其他語言,java語言還包括了更多的結構化資訊,這樣就為優化提供了更大的空間。還要記住,這些優化可以在運作時完成,即可以考慮到實際的應用特性。那麼什麼工作更适合于在編譯時完成而不是運作時完成呢?在此有一個權衡,即時間。

傳統jit編譯的問題在于,優化代碼會花費時間。是以,jit編譯器可以得到很好的結果,但是,在應用程式啟動的時候會有顯著的延遲。對于長時間運作的伺服器端應用程式來說,這通常不是個問題,而對于在計算能力有限的較小裝置上運作的用戶端軟體和應用程式來說,這卻是一個嚴重的問題。為了解決這個問題,java的編譯器技術(稱為hotspot)使用了一種叫做自适應編譯(adaptive compilation)的技巧。如果仔細檢視程式究竟花費時間做了哪些工作,可以發現它們往往會把絕大部分時間用于反複執行一部分代碼。這一反複執行的代碼塊可能僅僅是整個程式中很小的一部分,但是其表現卻決定了整個程式的性能。自适應編譯還允許java運作時利用一些新的類型的優化,而這些優化在一種靜态編譯型語言中是無法做到的,是以,java代碼号稱在某些情況下可以比c/c++運作得更快。

為了充分利用這一事實,hotspot仍以一個正常的java位元組碼解釋器“出場”,但在此存在一個差別:它在執行代碼時要對代碼進行測量(探查),進而檢視哪些部分得到了重複執行。一旦獲知對于性能有關鍵影響的代碼部分,hotspot将把這些代碼段編譯為真正的機器碼。由于它隻是将程式的一小部分編譯為機器碼,是以就有足夠的時間對這一部分加以優化。程式餘下的部分則根本無需編譯,隻要進行解釋即可,這樣可以節省記憶體和時間。實際上,java vm(虛拟機)可以以兩種模式運作:用戶端和伺服器,這分别決定了重點究竟是快速的啟動時間以及節省記憶體還是最佳的性能。

此時,一個自然而然的問題是,為何每次一個應用程式關閉的時候,要丢棄這些很好的探查資訊?sun在釋出java 5.0的時候,将共享的、隻讀的類持久地存儲到一個優化後的表單中,進而部分地回答了這個問題。這顯著地減少了啟動時間,以及在一台給定的機器上運作多個java應用程式的負擔。實作這一點的技術很複雜,但是,思路很簡單:即優化程式中需要快速運作的部分,而不需要擔心其他的部分。

本文僅用于學習和交流目的,不代表異步社群觀點。非商業轉載請注明作譯者、出處,并保留本文的原始連結。