引子 java
Java虛擬機是Java應用程序的執行環境。一般而言,JVM是由一組嚴格的指令集和一個複雜的內存模型來具體實現的虛擬機,它用來解釋編譯好的java字節碼文件,將字節碼轉換爲特定機器能夠執行的本機代碼(native code)。它也能夠指代某一軟件運行時的進程實例。這裏,咱們以hotspot實現的JVM爲例。 算法
JVM的規則保證任何一款具體實現的JVM都要以徹底相同的方式去解釋java字節碼文件,不管是一個進程,一個獨立的java操做系統,抑或是一個直接執行字節碼命令的處理器芯片。通常狀況下,咱們一般討論的JVM是一個運行在操做系統上的進程。 編程
JVM的架構設計使得它能夠精細的控制JAVA應用程序的每個動做,在沒有權限的狀況下,應用程序沒法去訪問本地文件系統,處理器,網絡等。例如,在遠程操做的狀況下,代碼須要有簽名證書。 bootstrap
除了去解釋java字節碼,許多軟件實現的JVM都有一個JIT編譯器用於生成頻繁執行的方法機器代碼。機器代碼是能夠直接被cpu解析執行的,因此比字節碼速度更快。 小程序
你無需去理解JVM的內部,就能編寫並運行一個JAVA應用程序。可是,若是你知道了其中的一些原理,就能避免一些性能上的問題。本文以sunspot爲例子來講明。 數組
架構 緩存
JVM主要有兩大子系統: 網絡
這裏的內存是底層操做系統分配給JVM的,以下所示: 架構
類加載器 併發
JVM應用不一樣類型的類加載器構造了層次結構:
當類加載器收到去加載一個類的請求時,會去檢查cache中該類是否已經被加載,而後向其父加載器發出加載請求,若是其父加載器加載失敗,那麼它就本身進行加載。一個子類加載器能夠檢查其父類加載器的cache中是否加載了某個類,可是父類加載器沒法查看子類cache中的緩存。這樣設計的緣由是爲了防止子類加載器加載那些已經被父類加載器加載過的類。(呼,好繞口。。。)
java文件通過編譯後會生成.class字節碼文件,它定義了JVM中的一個類型,包括域,方法,繼承信息,註解和其餘元數據。咱們知道,類是JVM能加載的最小程序代碼單元,將一個新的類加入到當前運行中的JVM中,首先要對類文件進行加載和鏈接,而後將一個表明該類的Class對象交給JVM,才能夠建立新的實例。
加載與鏈接
JVM要執行.class文件中的字節碼,首先必須以字節流的方式將文件讀入,而後轉化爲可使用的格式加入到運行的JVM中。這兩步被稱爲加載與鏈接。
加載
這個過程首先會建立一個字節數組,而後從文件系統中讀取構成類文件的字節流,最後產生與所加載類對應的Class對象。這個過程當中會對類作一些基本檢查,加載結束後,Class對象還不完整,因此類是不可用的。
鏈接
加載工做完成後,類須要被鏈接起來,這裏分爲3個階段:
鏈接與加載的最終產物是一個Class對象,它能夠表示加載並鏈接起來的新類型,能夠用它來建立新實例。
執行引擎
執行引擎負責執行被加載進內存的字節碼指令,爲了使計算機可以識別字節碼,執行引擎採用了兩種方式:
儘管JIT的編譯過程比普通的解釋過程要耗時,可是它只需編譯一次,對於那些上千次調用的方法來講,直接執行機器代碼就比每次都要轉換字節碼再執行要划算了。
JIT編譯器對於JVM而言並不是是必須的組件,同時,也不是提高JVM性能的惟一手段。JVM規範只是定義了字節碼與機器代碼的對應關係,至於如何具體實現,就是不一樣版本JVM的事情了。
內存模型
JAVA內存模型是創建在內存自動管理機制之上的。當一個對象不在被應用程序引用,垃圾收集器GC就丟棄它並釋放內存。這與其餘編程語言須要手動釋放對象的方式不一樣。
JVM從操做系統中申請來內存,並分割成以下幾個區域:
垃圾回收
內存自動管理是JAVA平臺最重要的組成部分。一個java進程既有棧又有堆,其中,棧保存了基本類型的局部變量,以及自定義類型變量在堆中存放的地址。堆中保存了要建立的對象。java對堆內存回收和再利用的基本算法被稱爲標記和清除。
最簡單的標記和清除算法首先會暫停全部正在運行的線程,而後堆中遍歷引用樹,標記出「活」的對象,遍歷完成後則清除回收全部未被標記的對象。其中,「活」的對象是指在任意用戶線程的棧幀中存在引用的對象。被清除的內存並不會還給OS,而是交給JVM。
JAVA對標記清除算法作了改進,採用「分代式垃圾收集」方法,由於對象的生存期或者很短或者很長,因此根據對象的生命週期將堆內存劃分爲不一樣區域,充分利用對象生命週期的特色。所以,同一個對象在其不一樣生命週期中,對它的引用可能指向了不一樣的內存區域。
將堆根據類實例的生存週期劃分爲不一樣區域使得內存管理更加有效,GC無需遍歷整個堆。絕大多數對象的生命週期都很短,而那些略長一些的對象所佔內存在程序結束以前不大可能被所有回收。
內存區域劃分
收集方式
對不一樣區域的內存回收方式是不一樣的,具體來說主要分爲年輕代收集和徹底收集。
年輕代收集
咱們將Eden區和Survivor Space稱爲年輕代,對這部份內存的清理與收集的過程很簡單:
徹底收集
當tenured區滿了,年輕代收集就沒法把對象放入tenured區了,這時候會觸發一次徹底收集。根據老年代所用的垃圾收集器,對老年代對象進行內部遷移。
發生一次 Major GC 至少伴隨一次Young GC,通常比JVM在tenured區申請不到內存,會進行Full GC。tenured區使用通常採用Concurrent-Mark–Sweep策略回收內存。
當一個GC運行時,應用程序全部的進程都將中止。Young GC很頻繁,可是會很快清理Eden池中的對象。而Major GC因爲涉及到大量仍存活的對象,因此比Young GC慢不少。
堆內存是動態的。當堆內存滿時,JVM會從新分配內存給它直到最大限度,同時也中止應用程序進程來完成內存分配。
線程
JVM是一個單進程,可是它能夠併發多個線程,不一樣線程執行本身的方法。全部的線程共享着JVM分配到的資源。JVM進程在程序入口(main方法)新開一個線程,其他的線程都來自與此線程,並獨立執行。多個線程能夠併發地在不一樣處理器中執行,或者共享同一個處理器。