Java虛擬機1:開篇


1.前言

由於后期學習需要用到大量的JVM底層的東西,所有本人調整了一下學習計划,打算先從JVM入手,了解整個JAVA的運行機制,內存模型,編譯原理等等一些底層的東西,這樣在學習 后面的東西,會有一種豁然開朗的感覺。后期的內容有從網上直接復制粘貼的內容,但是大部分的內容都是經過自己整理后的,我覺得參照別人寫的東西,未嘗不可。如果是轉載的文章,最后我列出轉載的地址。雖然我做不了技術的創造者,但是爭取做一個好的傳播者。

2.什么是Java

經過了多年的發展,Java早已由一門單純的計算機編程語言,演變為了一套強大的技術體系。是的,什么是Java,我想技術體系四個字應該是最好的概括了吧。Java設計者們將Java划分為3種結構獨立但卻彼此依賴的技術體系分支,它們分別對應着不同的規范集合和組件:

1、Java SE(標准版),主要活躍在桌面領域,主要包含了Java API組件。

2、Java EE(企業版),活躍在企業級領域,除了包含Java API組件外,還擴充有Web組件、事務組件、分布式組件、EJB組件、消息組件等,綜合這些技術,開發人員完全可以構建出一個具備高性能、結構嚴謹的企業級應用,並且Java EE也是用於構建SOA(面向服務架構)的首選平台。

3、Java ME(精簡版),活躍在嵌入式領域,稱之為精簡版的原因是,它僅保留了Java API中的部分組件,以及適應設備的一些特有組件。

上面講到Java技術體系的分支,那既然Java是一種技術體系,我們來看一下組成這種技術體系的技術:

1、Java編程語言

2、字節碼

3、Java API,包括Java API類庫和來自商業機構以及開源社區的第三方類庫

4、Java虛擬機

很多時候我們只關注了第一點,因為第一點才是和工作切實相關的。Java技術體系所包含的內容實際上Java官方有提供給我們一張圖.

3.Java的優點

Java能獲得如此廣泛的認可,除了它擁有一門結構嚴謹、面向對象的編程語言之外,還有許多不可忽視的優點:

1、它擺脫了硬件平台的束縛,實現了“一次編寫、到處運行”

2、它提供了一個相對安全的內存管理和訪問機制,避免了絕大部分的內存泄露和指針越界問題

3、它實現了熱點代碼檢測和運行時編譯及優化,這使得Java應用能隨着運行時間的增加而獲得更高的性能

4、它有一套完整的應用程序接口,還有無數來自商業機構和開源社區的第三方類庫來幫助它實現各種各樣的功能

5、它與身俱來對分布式技術的支持就比較完善

但是,Java最大的優勢和財富還不是以上這些,就像高翔龍老師在《Java虛擬機精講》中寫的,Java真正強大的地方是因為擁有全世界最多的技術擁護者和開源社區支持,他們無時無刻都保持着最充沛的體力與思維,一步一步地驅動着Java技術的走向。

4.JDK和JRE

兩個常見的重要概念。其實上面的圖中已經划分出了JDK和JRE的范圍了。我們對這張圖做一個歸納,用我們的語言簡單地總結一下什么是JDK和JRE:

1、JRE(Java Runtime Enviroment),是支持Java程序運行的標准環境,包含:java虛擬機、JAVA SE API、運行java應用程序所必須的文件等。

2、JDK(Java Development Kit),是用於支持Java程序開發的最小環境,包含:JRE的部分,以及編譯器和調試器等。

總結:

1.  如果只是要運行JAVA程序,只需要JRE就可以。 JRE通常非常小,也包含了JVM。

2.  如果要開發JAVA程序,就需要安裝JDK。 

OpenJDK

前面有講過,“Java真正強大的地方是因為擁有全世界最多的技術擁護者和開源社區支持,他們無時無刻都保持着最充沛的體力與思維,一步一步地驅動着Java技術的走向”。其實JDK在一開始並不是開源的,但是隨着開源運動的蓬勃發展,2006年Sun公司宣布將對Java開放源代碼,開源的Java平台開發主要集中在OpenJDK項目上。2009年4月15日,Sun公司正式發布OpenJDK,JDK 7則是Java開源后發布的第一個版本,任何組織和個人都可以為Java的發展做出貢獻。當然OpenJDK和真正的Oracle JDK(因為Sun公司被Oracle公司在2010年收購了嘛,所以就叫做Oracle JDK了)還是有區別的:

OpenJDK中的代碼基本上都來自於Oracle JDK,屬於Oracle JDK的一個分支,但是其中去除了一些非開源的組件和代碼,替換成了開源的組件和代碼,主要是加密和圖形的部分。因此用OpenJDK代替Oracle JDK可能會有一些的不兼容。

對於OpenJDK感興趣的,可以在OpenJDK官網http://download.java.net/openjdk/jdk7/下載OpenJDK的源代碼。像Java虛擬機HotSpot、Java編譯器Javac、JNI等等,源代碼都在里面。 

5.Java虛擬機(JVM)

5.1 概述

  JVM是JRE的一部分,實際上它是一個虛構出來的小型計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。JVM有自己完善的硬件架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。JAVA語言最大的特點就是跨平台運行,即所謂的“一次編寫,處處運行”,它屏蔽了與具體操作系統平台相關的信息,類似一個程序代碼和操作系統之間的一個中間件。

 Java程序的跨平台特性主要是指字節碼文件可以在任何具有Java虛擬機的計算機或者電子設備上運行,Java虛擬機中的Java解釋器負責將字節碼文件解釋成為特定的機器碼進行運行。因此在運行時,Java源程序需要通過編譯器編譯成為.class文件。眾所周知java.exe是java class文件的執行程序,但實際上java.exe程序只是一個執行的外殼,它會裝載jvm.dll(windows下,下皆以windows平台為例,linux下和solaris下其實類似,為:libjvm.so),這個動態連接庫才是java虛擬機的實際操作處理所在。

5.2 JVM的主要功能

下面我們先看一張圖,來了解JVM的主要功能和運行流程,如果看不懂沒關系,后期的文章會系列的討論。

三項主要功能:

  • 加載代碼:通過類加載器(class loader)加載類文件的過程,將class文件動態的加載到內存中。
  • 運行時數據區:JVM加載class文件和運行class文件的過程中,分配的空間,具體指上圖的jvm memory區域。
  • 執行代碼:負責執行class文件中包含的字節碼指令。

5.3 加載

5.3.1 類加載的過程

下面簡單介紹一下類加載的過程,類從被加載到虛擬機內存中開始,到卸載出內存為止,它的整個生命周期包括:加載、驗證、准備、解析、初始化、使用和卸載七個階段。它們開始的順序如下圖所示:

    其中類加載的過程包括了加載、驗證、准備、解析、初始化五個階段。其中驗證,准備,解析又可以合在一起。

   所以也可以分為三個大的步驟:裝載(Load),鏈接(Link)和初始化(Initialize)鏈接又分為三個步驟,如下圖所示:

 

1) 裝載:查找並加載類的二進制數據;

2) 鏈接

驗證:確保被加載類的正確性,就是確保.class字節碼符合虛擬機的要求;

准備:為類的靜態變量分配內存,並將其初始化為默認值,這些變量所使用的內存都將在方法區中分配

解析:虛擬機將常量池內的符號引用替換為直接引用的過程;

3)初始化:初始化過程是一個執行類構造器<clinit>()方法的過程,根據程序員通過程序制定的主觀計划去初始化類變量和其它資源。其實就是執行類構造器方法的內容,包括給static變量賦予用戶指定的值以及執行靜態代碼塊

注意:

在這五個階段中,加載、驗證、准備和初始化這四個階段發生的順序是確定的,而解析階段則不一定,它在某些情況下可以在初始化階段之后開始,這是為了支持Java語言的運行時綁定(也成為動態綁定或晚期綁定)。另外注意這里的幾個階段是按順序開始,而不是按順序進行完成,因為這些階段通常都是互相交叉地混合進行的,通常在一個階段執行的過程中調用或激活另一個階段。

    這里簡要說明下Java中的綁定:綁定指的是把一個方法的調用與方法所在的類(方法主體)關聯起來,對java來說,綁定分為靜態綁定和動態綁定: 

  • 靜態綁定:即前期綁定。在程序執行前方法已經被綁定,此時由編譯器或其它連接程序實現。針對java,簡單的可以理解為程序編譯期的綁定。java當中的方法只有final,static,private和構造方法是前期綁定的。
  • 動態綁定:即后期綁定,也叫運行時綁定。在運行時根據具體對象的類型進行綁定。在java中,幾乎所有的方法都是后期綁定的。

5.3.2 類加載器

(1): 類加載器的結構

   說到加載,不得不提到類加載器,下面就具體講述下類加載器。下面看一張圖了解一下類加載器的層次結構。

下面是類加載器的執行的各自不同的目錄下的  .jar 包:

看上圖我們知道類加載器有以下幾類:

  • 啟動類加載器:Bootstrap ClassLoader,在JVM啟動的時候加載,使用native code實現(Hotspot虛擬機(C++)),是虛擬機自身的一部分。它負責加載java的核心庫,即存放在JDK\jre\lib(JDK代表JDK的安裝目錄,下同)下,或被-Xbootclasspath參數指定的路徑中的,並且能被虛擬機識別的類庫(如rt.jar,所有的java.*開頭的類均被Bootstrap ClassLoader加載)。此類加載器並不繼承java.lang.ClassLoader,不能被java程序直接調用。
  • 擴展類加載器:Extension ClassLoader,該類由sun.misc.Launcher$ExtClassLoader實現,它負責用於加載JAVA_HOME/lib/ext目錄中的,或者被java.ext.dirs系統變量指定所指定的路徑中所有類庫,就是除了基本的Java API以外的擴展類,比如 security的安全擴展功能,開發者可以直接使用擴展類加載器
  • 應用程序類加載器:Application ClassLoader,一般也被稱為系統類加載器(System class loader),該類加載器由sun.misc.Launcher$AppClassLoader來實現,它負責加載用戶類路徑($ClassPath)所指定的類(即bin目錄下的.class文件),開發者可以直接使用該類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器
  • 用戶自定義類加載器(User-defined class loader):這是應用程序開發者用直接用代碼實現的類裝載器。也可以用來加載應用類,使用自定義的類加載器有很多特殊的原因:運行時重新加載類或者把加載的類分隔為不同的組,典型的用法比如 web 服務器 Tomcat。

(2):類加載器的雙親委托模型

     上面層次關系稱為類加載器的雙親委派模型。我們把每一層上面的類加載器叫做當前層類加載器的父加載器,當然,它們之間的父子關系並不是通過繼承關系來實現的,而是使用組合關系來復用父加載器中的代碼。該模型在JDK1.2期間被引入並廣泛應用於之后幾乎所有的Java程序中,但它並不是一個強制性的約束模型,而是Java設計者們推薦給開發者的一種類的加載器實現方式。

    雙親委派模型的工作流程是:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委托給父類加載器去完成,依次向上,層層遞進,最終所有的類加載請求最終都應該被傳遞到頂層的啟動類加載器中,只有當父加載器在它的搜索范圍中沒有找到所需的類時,即無法完成該加載,子加載器才會嘗試自己去加載該類。

優點:

    使用雙親委派模型來組織類加載器之間的關系,有一個很明顯的好處,就是Java類隨着它的類加載器(說白了,就是它所在的目錄)一起具備了一種帶有優先級的層次關系,這對於保證Java程序的穩定運作很重要。例如,類java.lang.Object類存放在JDK\jre\lib下的rt.jar之中,因此無論是哪個類加載器要加載此類,最終都會委派給啟動類加載器進行加載,這邊保證了Object類在程序中的各種類加載器中都是同一個類。

(3): 類加載器的特點

 Java提供了動態的加載特性;它會在運行時的第一次引用到一個class的時候對它進行加載和鏈接,而不是在編譯期進行。JVM的類裝載器負責動態加載。

    Java類裝載器有如下幾個特點:

    •  層級結構:Java里的類裝載器被組織成了有父子關系的層級結構。Bootstrap類裝載器是所有裝載器的父親。
    • 代理模式:基於層級結構,類的裝載可以在裝載器之間進行代理。當裝載器裝載一個類時,首先會檢查它是否在父裝載器中進行裝載了。如果上層的裝載器已經裝載了這個類,這個類會被直接使用。反之,類裝載器會請求裝載這個類。
    • 可見性限制:一個子裝載器可以查找父裝載器中的類,但是一個父裝載器不能查找子裝載器里的類。
    • 不允許卸載:類裝載器可以裝載一個類但是不可以卸載它,不過可以刪除當前的類裝載器,然后創建一個新的類裝載器。

5.4 運行時數據區

運行時數據區是在JVM運行的時候操作系統所分配的內存區。它可以划分為幾個區域:方法區,堆,java棧,pc寄存器,本地方法棧等。

5.5 執行引擎(Execution Engine

5.5.1 執行引擎的定義

     通過類裝載器裝載的,被分配到JVM的運行時數據區的字節碼會被執行引擎執行。執行引擎以指令為單位讀取Java字節碼。它就像一個CPU一樣,一條一條地執行機器指令。每個字節碼指令都由一個1字節的操作碼和附加的操作數組成。執行引擎取得一個操作碼,然后根據操作數來執行任務,完成后就繼續執行下一條操作碼。在執行引擎執行時,有一個任務是必須把字節碼轉換成可以直接被JVM執行的語言,也就是機器碼。

5.5.2 字節碼的執行技術

主要的執行技術有:解釋,即時編譯,AOT編譯等幾種。

其中編譯和解釋執行的技術有時候是混合在一起的,如果單獨從編譯技術的角度來看,又分為:前端編譯、即時編譯(JIT編譯)、靜態提前編譯(AOT編譯)三種。

  • 解釋器:在解釋執行前,java編譯器已經將java源碼文件(.java)編譯成class二進制字節碼文件。解釋器一條一條的讀取二進制字節碼文件,解釋並且執行字節碼指令。缺點是執行速度比較慢.
  • 即時(Just-In-Time)編譯器:即時編譯器被引入用來彌補解釋器的缺點。執行引擎首先按照解釋執行的方式來執行,然后通過在運行時通過收集監控信息,檢查方法的執行頻率,如果一個方法的執行頻率超過一個特定的值的話,那么這個方法就會被編譯成本地代碼,放到緩存里,下次執行引擎就沒有必要再去解釋執行這個方法了,直接通過本地代碼去執行它,因為本地代碼是保存在緩存里的。這種方式也被稱為“熱點”編譯它之所以被稱作”熱點“是因為熱點編譯器通過分析找到最需要編譯的“熱點”代碼,然后把熱點代碼編譯成本地代碼。如果已經被編譯成本地代碼的字節碼不再被頻繁調用了,換句話說,這個方法不再是熱點了,那么Hotspot VM會把編譯過的本地代碼從cache里移除,並且重新按照解釋的方式來執行它。優點是執行效率大大增加,缺點就是編譯代碼所花的時間要比用解釋器去一條條解釋執行花的時間要多。
  • AOT(Ahead-Of-Time)編譯器。它使得多個JVM可以通過共享緩存來共享編譯過的本地代碼,程序運行前,直接把Java源碼文件(.java)編譯成本地機器碼的過程;

目前主要還是采用解釋器+JIT編譯這種混合的方式,如JDK中的HotSpot虛擬機。 AOT的編譯器在IBM JDK1.6的時候被引入進去,不過主流的jdk用的還是比較少。另外JIT編譯速度及編譯結果的優劣,是衡量一個JVM性能的很重要的指標,所以對程序運行性能優化集中到這個階段;也就是說可以對這個階段進行JVM調優;

HotSpot JVM 內置了兩個不同的即時編譯器,分別稱為Client Compiler(C1)和Server Compiler(C2),HotSpot默認采用解釋器和其中一個編譯器直接配合的方式工作,使用哪個編譯器取決於虛擬機運行的模式,HotSpot會根據自身版本和宿主機器硬件性能自動選擇模式C1還是C2,用戶也可以使用“-client”或”-server”參數去指定。

6.后記

研究jvm,並不是需要我們能寫一個jvm,而是要求我們最起碼對代碼的執行有一個比較清晰的認識,當以后遇到程序比較復雜的場景,可以根據我們的業務需求定制自己的虛擬機,對虛擬機進行調優。

目前我們現在說的Java虛擬機基本上都是JDK自帶的虛擬機HotSpot,這款虛擬機也是目前商用虛擬中市場份額最大的一款虛擬機,可以通過在命令行程序中輸入“java -version”來查看:

那其實市面上還有很多別的優秀的虛擬機。Sun公司除了有大名鼎鼎的HotSpot外,還有KVM、Squawk VM、Maxine VM,BEA公司有JRockit VM、IBM公司有J9 VM等等。

 

轉載地址:http://www.cnblogs.com/xrq730/p/4826691.html

http://blog.csdn.net/zhoudaxia/article/details/26454421/

http://www.cnblogs.com/insistence/p/5901457.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM