前情回顧在本專欄的前12篇博客中, 我們主要大致介紹了什么是JVM, 并且詳細介紹了class文件的格式。 對于深入理解Java, 或者深入理解運行于JVM上的其他語言, 深入理解class文件格式都是必須的。 如果讀者對class文件的格式不是很熟悉, 在閱讀本博客下面的文章之前, 建議先讀一下前面的12篇博客, 或者參考其他資料, 熟悉class文件的格式。
在深入理解Java虛擬機到底是什么 這篇博客中, 我們有提到過, JVM就是一個特殊的進程, 我們執(zhí)行的java程序, 都運行在一個JVM進程中, 這個進程的作用就是加載class文件, 并且執(zhí)行class文件中的代碼。 當(dāng)然, 從一個class文件的加載, 到準(zhǔn)備好可執(zhí)行之前, 還有一段很長的路要走, 以后的文章會詳細介紹這個過程。 既然虛擬機作為一個虛擬的計算機, 來執(zhí)行我們的程序, 那么在執(zhí)行的過程中, 必然要有地方存放我們的代碼(class文件); 在執(zhí)行的過程中, 總會創(chuàng)建很多對象, 必須有地方存放這些對象; 在執(zhí)行的過程中, 還需要保存一些執(zhí)行的狀態(tài), 比如, 將要執(zhí)行哪個方法, 當(dāng)前方法執(zhí)行完成之后, 要返回到哪個方法等信息, 所以, 必須有一個地方來保持執(zhí)行的狀態(tài)。 上面的描述中, “地方”指的當(dāng)然就是內(nèi)存區(qū)域, 程序運行起來之后, 就是一個動態(tài)的過程, 必須合理的劃分內(nèi)存區(qū)域, 來存放各種數(shù)據(jù)。 所以, 在本文中, 將會詳細介紹JVM的運行時數(shù)據(jù)區(qū)。
JVM體系結(jié)構(gòu)和運行時數(shù)據(jù)區(qū)概述要理解JVM的運行時數(shù)據(jù)區(qū), 必須先要理解JVM的體系結(jié)構(gòu), 因為虛擬機的體系結(jié)構(gòu)基本上解釋了“為什么會有這些運行時數(shù)據(jù)區(qū)” 。 在深入理解Java虛擬機到底是什么 這篇文章中也簡單的提到過JVM的體系機構(gòu), 這里再詳細的講解一下。 JVM的體系結(jié)構(gòu)如下:
由此可見, 運行時數(shù)據(jù)區(qū)的劃分, 是和JVM的體系結(jié)構(gòu)相關(guān)的。 本文主要介紹運行時數(shù)據(jù)區(qū)的劃分, 對體系結(jié)構(gòu)不做深入的講解。 簡單概括一下, 類加載器子系統(tǒng)用于將class文件加載到虛擬機的運行時數(shù)據(jù)區(qū)中(準(zhǔn)確的說應(yīng)該是方法區(qū)) 。 可以認為執(zhí)行引擎是字節(jié)碼的執(zhí)行機制, 一個線程可以看做是一個執(zhí)行引擎的實例。 下面介紹運行時數(shù)據(jù)區(qū): JVM運行時數(shù)據(jù)區(qū)方法區(qū)在字面意思上, “方法區(qū)”這個詞會讓人產(chǎn)生誤解。因為方法區(qū)存放的不只是方法, 它存放的是類型信息。我們在寫程序的時候, 幾乎總是在和類, 對象打交道, 我們知道根據(jù)一個類可以創(chuàng)建對象。 一般來說, 我們操縱的是對象, 訪問對象的屬性, 調(diào)用對象的方法等, 但是我們要思考這樣一個問題, 虛擬機根據(jù)什么信息知道如何創(chuàng)建對象的呢? 當(dāng)然是根據(jù)這個對象的類型信息, 但是這個類型信息在哪里呢?現(xiàn)在我們知道是在方法區(qū)中。 那么類型信息是被誰加載到方法區(qū)中的呢?由上面的體系結(jié)構(gòu)圖, 我們可以知道是類加載器子系統(tǒng)?那么所謂的類型信息, 都包含什么信息呢?這些信息又是如何存放的呢?這里的類型信息, 可以籠統(tǒng)的認為就是我們前面講解過的一個class文件,類加載器子系統(tǒng)將會提取class文件里面的類型信息,并將這些類型信息存放到方法區(qū)中。 至于方法區(qū)中如何存放一個類型數(shù)據(jù), 是和JVM的具體實現(xiàn)相關(guān)的。 但是不管如何實現(xiàn), 一個類的類型信息總是會包含如下信息:類的全限定名 當(dāng)前類的直接父類的全限定名 這個類是接口類型, 類類型, 還是枚舉類型 類的訪問修飾符信息 當(dāng)前類型的超接口的全限定名 當(dāng)前類型的常量池 字段信息 方法信息 如果對class文件格式比較熟悉的話, 可以看出, 這些信息都是在class文件中描述過的。 由于我們無法看到類型信息具體是如何存儲的, 但是大致可以將類型信息看做一個class文件, 這有助于我們的理解。下面再次列出class文件結(jié)構(gòu)的表格,讀者可以對比class文件中的內(nèi)容到類型數(shù)據(jù)上, 該表中的各種數(shù)據(jù)已經(jīng)在前面的博客中詳細講解過:
類型數(shù)據(jù)中,除了這些基本信息外, 類型信息還包括以下兩個方面: 靜態(tài)變量存儲區(qū)
而靜態(tài)變量也是存在于類型信息中, 可以這么說, 類型信息中, 會有專門的區(qū)域存放類的靜態(tài)變量。 與存在于對象中的實例變量不同, 靜態(tài)變量存在于類型數(shù)據(jù)中, 每個類型只有一份,所以也叫類變量。
方法區(qū)是一個相對來說比較固定的內(nèi)存區(qū), 因為它存放的是類型信息, 而類型信息在被加載到方法區(qū)中之后, 除了必要的連接和初始化, 一般不會有較大改動,一般情況下, JVM也不會卸載類型信息, 所以方法區(qū)也可以稱為JVM的靜態(tài)區(qū)。 一個類型的生命周期一般就是整個程序的生命周期。 這也是為什么要慎用靜態(tài)變量的原因所在, 因為靜態(tài)變量隨類型信息存放在方法區(qū)中, 生命周期很長, 如果使用不當(dāng), 很容易造成內(nèi)存泄露。 一個JVM實例中只存在一個方法區(qū), 方法區(qū)中的所有類型數(shù)據(jù)被所有線程共享。
堆方法區(qū)是存放類型數(shù)據(jù)的, 而堆則是存放運行時產(chǎn)生的對象的。 和C++不同的是, Java只能在堆中存放對象, 而不能在棧上分配對象, 所有運行時產(chǎn)生的對象全部都存放于堆中, 包括數(shù)組。 我們知道, 在Java中, 數(shù)組也是對象。一個JVM實例中只有一個堆, 所有線程共享堆中的數(shù)據(jù)(對象) 。
Java虛擬機支持幾種不同的創(chuàng)建對象的指令, 如new , anewarray等。 這些指令執(zhí)行的結(jié)果就是在堆中分配內(nèi)存, 并創(chuàng)建對象。 但是Java虛擬機的指令集中并不包含任何釋放內(nèi)存的指令, 因而我們也就不能手動釋放內(nèi)存。 所有被創(chuàng)建的對象都會被一個叫做垃圾收集器(GC)的模塊自動回收, 垃圾收集器有不同的實現(xiàn)方式, 他們以 特定的方式判斷對象是否過期, 并以特定的方式對對象進行回收, 關(guān)于垃圾收集的話題不是本文的重點, 這里就不多說了。
我們只要知道:所有創(chuàng)建的對象都存在堆中, 而垃圾收集器會自動回收過期的對象, 所以,JVM的堆區(qū)是垃圾收集器的“重點管理區(qū)” 。
Java棧Java棧是一個線程的執(zhí)行區(qū)域, 它保存著一個線程中的方法的調(diào)用狀態(tài), 也可以說, 一個Java線程的運行狀態(tài), 都由一個Java棧來保存。 在這個棧中, 每一方法對應(yīng)一個棧幀, 請注意區(qū)分棧幀和棧這兩個概念。 棧指的是整個線程的執(zhí)行棧, 棧幀是棧中的一個單位, 每個方法對應(yīng)一個棧幀。 JVM會對Java棧執(zhí)行兩種操作: 壓棧和出棧。 這兩種操作在執(zhí)行時都是以幀(棧幀)為單位的。 當(dāng)調(diào)用了一個新的方法, 就會壓入一個棧幀, 當(dāng)一個方法調(diào)用完成,
就會彈出這個方法的棧幀, 回到調(diào)用者的棧幀。
舉例來說, 如果方法a調(diào)用了方法b, 而方法b中調(diào)用了方法c。 這個過程中的方法調(diào)用和返回的裝狀態(tài)是這樣的(其中圖中兩條虛線之間表示Java棧,每個方塊表示一個特定方法的棧幀)
Java棧上的所有數(shù)據(jù)都是線程私有的, 也就是說, 每個線程都會有自己的Java棧, 不會相互訪問其他Java棧中的數(shù)據(jù)。
PC寄存器pc寄存器用于存放一條指令的地址, 這條指令就是虛擬機要執(zhí)行的下一條指令。pc寄存器和線程相關(guān)聯(lián), 每一個線程都有一個PC寄存器。
本地方法棧我們知道Java可以和C/C++互調(diào)。如果當(dāng)前線程執(zhí)行的代碼是C/C++寫的本地代碼, 那么這些方法就在本地方法棧中執(zhí)行,而不會在Java棧中執(zhí)行, Java棧中只執(zhí)行Java方法。
|
|