世界上有一半以上的程式員在使用JAVA編程,然後有一般以上的服務都採用JAVA虛擬機器來運行,然後,我們真正對它瞭解有多少,多數人在編碼的過程中可能都會忽視這個問題,當然也包括我,做了這麼多年的程式員,寫了也不少代碼,但問題是,回想起來個人對其理解的程度也不咋的,所以今天利用這個時間來對JVM簡單的描述下,我們既然要瞭解JAVA虛擬機器,當然我們首先應該去瞭解它的體繫結構,以及運行原理,體繫結構的話,我在這裡就不畫圖了,網上有很多,在這裡,我也CP一張吧,這下面的圖是正確的:
從總體結構上看,上面可大體分為五部分來體現,第一部分是類裝載子系統,第二部分是運行資料區,第三部分是執行引擎,第四部分是本地方法介面,第五部分就是本地方法庫。OK,這是總體結構的劃分,細分每個結構吧:
第一部分:類裝載子系統,主要的工作就是為載入類檔案,負責把相關的類載入進來
第二部分:運行資料區,可能這也是JVM裡包括的內容最多也最複雜的部分,為什麼呢,我們細下來看圖,圖中分為五小部分,方法區,JAVA棧,JAVA堆,程式計數器,本地方法棧。
好,現在我們來詳細分解下這個所為最複雜的部分,首先我們來理解,我們為什麼需要這個方法區的存在,想想方法區的字面含義理解應該是資訊儲存的展示區吧,不知道我的理解對不對,也就是說在我們通過類裝載子系統把相關的類裝載進運行資料區時,其類會經過一個編譯器編譯的過程,當編譯器編譯後會出現一些相關的資訊,如屬性,常量,方法等,然而這些資訊進入運行資料區後存放位置如何呢,首先它們考慮了方法區,畢竟方法去就是用來存放其相關資訊的,然而此時並可能不會有任何堆棧記憶體的分配,只是還需要進一步去走流程與分解,這時一旦資訊進入方法去後,想想,檢查下是否有相關的指令集的聲明,如JAVA的基礎資料型別 (Elementary Data Type),指令代碼,以及常量等,若有就要求JAVA棧分配其定長空間來儲存,在這裡說到棧了,我們來理解下棧,什麼是JAVA棧呢,簡單的來說,JAVA棧其實就是一個定長記憶體指令儲存地區,它的優點是什麼,優點有,儲存速度快,對記憶體管理簡單,對指令集記憶體配置為定長分配,而且是按順序儲存的,即以壓棧與彈棧的方式來進行簡單操作,還有沒有別的優點呢,先別急,等我們看來JAVA堆對比以下就知道了,前面說到類裝載後編譯的資訊首先儲存在方法區,其次以選定的方式進行對棧作來相應分配,當然,這時候還會作另一個檢測,就是該類是否有對其執行個體對象,若有,不好意識,執行個體對象對記憶體配置是屬於動態改變的,想要棧來支援,沒門,棧不答應,因為那樣違反了棧的儲存原則,沒辦法就只能去找JAVA堆了,因為聽說只有JAVA堆才支援記憶體動態分配,OK,JAVA堆答應了,但是問題來,你這個執行個體對象咋就那麼千變萬化呢,你的資料量總是在變化,而且有時候我需要給你分配很大的記憶體空間,甚至你不用了,你還棧著的可能性都有,不行,這是堆得想辦法對其執行個體對象進行檢查,要是我給其分配的記憶體空間,它不用了,咋就給它收回來,這樣也可以免得過分浪費資源嘛,這時候堆就整了個GC(記憶體回收)機制,GC主要是來監控其在堆中執行個體的對象是否一直佔用其記憶體,若沒有,就對其收回,想到這裡,在堆中執行個體的都是動態資料啊,沒有相應的標記來檢查,而且堆中的記憶體是屬於其所有線程所共用,這可怎麼辦,GC後面想了個辦法,棧不是存放指令的地方嗎,而且管理與操作都很簡單,又效力,想想,可以這樣做,每到堆棧中執行個體的對象,都需要在棧中儲存一個執行堆中執行個體對象的4位元組指令碼,它們是一一對應的,這樣,我就可以通過在棧中的指令集來很快速與方便的尋找到在堆中執行個體的對象資料了,OK,問題解決了,相對於說,在棧中的指令指向堆中的的執行個體,這個指令就好比是一個地址指標,就類似於一個信封地址一樣,在這裡我們也就把堆給順便講解了,在細一點吧,在JAVA編碼中,我們長會用static這個修飾符,在JAVA中也就是所為的靜態與非靜態區別,然而我們怎麼去區分它們呢,這可能需要我們去結合JAVA中的堆棧來進行講解,大家都知道,靜態方法是可以直接調用的,而非靜態方法是需要執行個體才能訪問的,在我們細下裡看DALVIK的源碼時,都會發現在需要執行個體的非靜態方法時會有一個預設的隱含參數的操作,當然這個隱含參數不是自己給的,是由JVM來分配的,沒執行個體一個非靜態方法,JVM都會給該執行個體在STACK中給出一個預設的隱含參數指令來指向堆中執行個體化了的對象,這樣就可以通過這個指令來尋找到在對象動態執行個體的對象了,當然,這裡我們可能還會牽涉到程式計數器,它做什麼用的呢,它主要是對其位元組碼的行號作指標,也就是說,在一個class檔案被load進虛擬機器後,方法指令儲存在棧中,堆中此刻是沒有資料的,此時程式計數器開始執行指令,如果是靜態方法就直接執行指令代碼,此時指令代碼是不能訪問堆中的資料區域的,如果是非靜態方法,由於隱含參數沒有值,也就無法通過棧中的指令去檢查到堆中的資料,也就會報錯,這時候必須對其非靜態方法進行執行個體才是,然後把在棧中分配的指令給非靜態方法,這樣程式計數器就依次執行指令就檢查到堆中執行個體的相應資料區域了。在這裡,我們需要理解下靜態方法與動態屬性,然動態屬性需要棧中的指令才能發現去堆中執行個體對象資料區域,在這裡我們不難知道,靜態屬性可能也是放在棧中儲存了,這樣定長的資料很容易算出位移量,為此不管類的什麼方法,都可以訪問到類的靜態屬性,此就成來全域屬性的稱謂,當然執行個體對象的方法指令也是儲存到棧中的,這點得稍微注意以下。剩下的就是本地方法棧了,它我們又該怎麼去理解呢,其實這個本地方法棧很好理解,簡單的描述就是服務於本地方法的通訊方式。
好了上面是對運行資料區的理解,還有執行引擎,與本地方法介面和本地方法庫,這幾個問題,我就不詳細講解,因為它們存在的理由很簡單就是一個適配過程。
介紹完以上,我們就結合之前講解的策略模式來對其簡單的描述下吧,當然我的理解並不一定正確,只是拿來作參考罷了,前面我們講解的策略模式是以錦囊妙計的話題來展開講解的,用在這裡也無妨,類似於JVM的運行資料區就是一個錦囊,它包括了很多妙計的執行,只是一般的妙計的是獨立執行的,但最終都有一個目的就是去完成這總的過程才是,如在錦囊妙計中,目的就是讓劉備去江東安全的把老婆娶回來,然JVM中的過程目的就是把裝載的類穩定的走起來,並獲得結果,類似於方法區,程式計數器,棧,堆,本地方法棧等都是其妙計之一,只是功能表現不同罷了,OK,在執行引擎相對來說就是趙雲,二類裝載器好像就是諸葛亮(這個比喻可能不太好),其本地方法介面就類似於執行操作規則,OK,這樣比喻不知道正確否,說了這麼多,唯一想表達的是,我們在編碼過程中不僅僅只是知道代碼可以編出來就行了,還要去理解它為什麼要這麼編,這也算是我多年來的經驗總結吧,尤其是在JAVA中我們可能太少去考慮記憶體配置與演算法最佳化等問題,我這些在以後我的程式生涯中需要隨時警惕才是,因為我一生的夢想就是做一個真正合格的軟體工程師,二不僅僅只是一個會寫代碼的程式員,我想我們都應該是,在下節,我順便講解下JVM的GC吧,