JVM的藝術—JAVA內存模型

*喜歡文章,動動手指點個贊 *java

引言

親愛讀者大家好,關於jvm篇章的連載,前面三章講了類加載器,本篇文章將進入jvm領域的另外一個知識點,java內存模型。完全的瞭解java內存模型,是有必要的。只要掌握了java的內存模型,內存空間分爲哪些區域,才能更好地理解,java是如何建立對象以及如何分配對象的空間。對後續的jvm調優打下堅實的基礎。而對於如今的互聯網行業來講,高併發,高可用已經必不可少,而學好jvm調優,不只能在企業工做當中針對高併發場景下的系統進行優化,在平常對系統的錯誤排查、系統的優化也起着相當重要的做用。但願這篇文章能讓各位讀者學到真正的本領。同時也感謝你們的持續關注和承認。面試

一:JDK體系結構算法

JDK、JRE、JVM之間的關係

JDK:Java Development Kit(java開發工具包),包含JRE和開發工具包,例如javac、javah(生成實現本地方法所需的 C 頭文件和源文件)。
JRE:Java Runtime Environment(java運行環境),包含JVM和類庫。
JVM:Java Virtual Machine(Java虛擬機),負責執行符合規範的Class文件。小程序

Java語言的跨平臺特性

JVM所處的位置

(1)一般工做中所接觸的基本是Java庫和應用以及Java核心類庫,知道如何使用就能夠了,可是歸根結底代碼都是要編譯成class文件由Java虛擬機裝載執行,所產生的結果或者現象均可以經過Java虛擬機的運行機制來解釋。一些相同的代碼會因爲虛擬機的實現不一樣而產生不一樣結果。數組

(2)在Java平臺的結構中,能夠看出,Java虛擬機(JVM)處在覈心的位置,是程序與底層操做系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操做系統,其中依賴於平臺的部分稱爲適配器;JVM經過移植接口在具體的平臺和操做系統上實現;在JVM的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application)和小程序(Java applet)能夠在任何Java平臺上運行而無需考慮底層平臺,就是由於有Java虛擬機(JVM)實現了程序與操做系統的分離,從而實現了Java的平臺無關性。網絡

(3)對JVM規範的的抽象說明是一些概念的集合,它們已經在書《The Java Virtual Machine Specification》(《Java虛擬機規範》)中被詳細地描述了;對JVM的具體實現要麼是軟件,要麼是軟件和硬件的組合,它已經被許多生產廠商所實現,並存在於多種平臺之上;運行Java程序的任務由JVM的運行期實例單個承擔。多線程

(4)JVM能夠由不一樣的廠商來實現。因爲廠商的不一樣必然致使JVM在實現上的一些不一樣,像國內就有著名的TaobaoVM;然而JVM仍是能夠實現跨平臺的特性,這就要歸功於設計JVM時的體系結構了。架構

(5)JVM在它的生存週期中有一個明確的任務,那就是裝載字節碼文件,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行,即Java程序被執行。所以當Java程序啓動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟着消失了。併發

Class字節碼

編譯後被Java虛擬機所執行的代碼使用了一種平臺中立(不依賴於特定硬件及操做系統的)的二進制格式來表示,而且常常(但並不是絕對)以文件的形式存儲,所以這種格式被稱爲Class文件格式。Class文件格式中精確地定義了類與接口的表示形式,包括在平臺相關的目標文件格式中一些細節上的慣例,
正如概念所說,Java爲了可以實現平臺無關性,制定了一套本身的二進制格式,並常常以文件的方式存儲,稱爲Class文件。這樣在不一樣平臺上,只要都安裝了Java虛擬機,具有Java運行環境[JRE],那麼均可以運行相同的Class文件。app

上圖描述了Java程序運行的一個全過程,也能夠看出Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫並編譯的程序能夠運行在這個平臺上。
由Java源文件編譯生成字節碼文件,這個過程很是複雜,學過《編譯原理》的朋友都知道必須通過詞法分析、語法分析、語義分析、中間代碼生成、代碼優化等;一樣的,Java源文件到字節碼的生成也想要經歷這些步驟。Javac編譯器的最後任務就是調用con.sun.tools.javac.jvm.Gen類將這課語法樹編譯爲Java字節碼文件。
其實,所謂的編譯字節碼,無非就是將符合Java語法規範的Java代碼轉化爲符合JVM規範的字節碼文件。JVM的架構模型是基於棧的,大部分都須要經過棧來完成。
字節碼結構比較特殊,其內部不包含任何的分隔符,沒法人工區分段落(字節碼文件自己就是給機器讀的),因此不管是字節順序、數量都是有嚴格規定的,全部16位、32位、64位長度的數據都將構形成2個、4個、8個-----8位字節單位來表示,多字節數據項老是按照Big-endian順序(高位字節在地址的最低位,地位字節在地址的最高位)來進行存儲。
參考《Java虛擬機規範 Java SE7版》的描述,每個字節碼其實都對應着全局惟一的一個類或者接口的定義信息。字節碼文件才用的是一種相似於C語言結構體的僞結構來描述字節碼文件格式。字節碼文件中對應的「基本類型」u1,u2,u4,u8分別表示無符號一、二、四、8個字節。

Class文件----整體格式

值得一提的是,一個有效的class字節碼文件的前4個字節爲0xCAFEBABE,都是固定的,被稱爲「魔術」,即magic。它就是JVM用於校驗所讀取的目標文件是不是一個有效且合法的字節碼文件。因而可知,JVM並非經過判斷文件後綴名的方式來校驗,以防止人爲手動修改。

JVM底層架構圖

上面這張圖,是本人花了不少心思總結出來的,基本涵蓋了java內存模型的結構。今天奉上。這篇文章會把上面這張圖講清楚。

運行時數據區:

1,堆

Java堆在虛擬機啓動的時候被建立,Java堆主要用來爲類實例對象和數組分配內存。Java虛擬機規範並無規定對象在堆中的形式。
在Java中,堆被劃分紅兩個不一樣的區域:新生代( Young )、老年代( Old );這也就是JVM採用的「分代收集算法」,簡單說,就是針對不一樣特徵的java對象採用不一樣的 策略實施存放和回收,天然所用分配機制和回收算法就不同。新生代( Young ) 又被劃分爲三個區域:Eden、From Survivor、To Survivor。

分代收集算法:採用不一樣算法處理[存放和回收]Java瞬時對象和長久對象。大部分Java對象都是瞬時對象,朝生夕滅,存活很短暫,一般存放在Young新生代,採用複製算法對新生代進行垃圾回收。老年代對象的生命週期通常都比較長,極端狀況下會和JVM生命週期保持一致;一般採用標記-壓縮算法對老年代進行垃圾回收。
這樣劃分的目的是爲了使JVM可以更好的管理堆內存中的對象,包括內存的分配以及回收。
  Java堆可能發生以下異常狀況:若是實際所需的堆超過了自動內存管理系統能提供的最大容量,那Java虛擬機將會拋出一個OutOfMemoryError異常。簡稱(OOM)。

堆大小 = 新生代 + 老年代。堆的大小可經過參數–Xms(堆的初始容量)、-Xmx(堆的最大容量) 來指定。

其中,新生代 ( Young ) 被細分爲 Eden 和 兩個 Survivor 區域,這兩個 Survivor 區域分別被命名爲 from 和 to,以示區分。默認的,Edem : from : to = 8 : 1 : 1 。(能夠經過參數 –XX:SurvivorRatio 來設定 。

即: Eden = 8/10 的新生代空間大小,from = to = 1/10 的新生代空間大小。

JVM 每次只會使用 Eden 和其中的一塊 Survivor 區域來爲對象服務,因此不管何時,老是有一塊 Survivor 區域是空閒着的。

新生代實際可用的內存空間爲 9/10 ( 即90% )的新生代空間。

java堆是GC垃圾回收的主要區域。 GC分爲兩種: Minor GC、Full GC(也叫作Major GC)

Minor GC(簡稱GC)
Minor GC是發生在新生代中的垃圾收集動做, 所採用的是複製算法。
GC通常爲堆空間某個區發生了垃圾回收,
新生代(Young)幾乎是全部java對象出生的地方。即java對象申請的內存以及存放都是在這個地方。java中的大部分對象一般不會長久的存活, 具備朝生夕死的特色。
當一個對象被斷定爲「死亡」的時候, GC就有責任來回收掉這部分對象的內存空間。
新生代是收集垃圾的頻繁區域。

2,方法區(元空間)

方法區在虛擬機啓動的時候被建立,它存儲了每個類的結構信息,例如運行時常量池、字段和方法數據、構造函數和普通方法的字節碼內容、還包括在類、實例、接口初始化時用到的特殊方法。
方法區可能發生以下異常狀況: 若是方法區的內存空間不能知足內存分配請求,那Java虛擬機將拋出一個OutOfMemoryError異常.

3,JVM棧空間

每一個Java虛擬機線程都有本身的Java虛擬機棧。Java虛擬機棧用來存放棧幀,而棧幀主要包括了:局部變量表、操做數棧、動態連接。Java虛擬機棧容許被實現爲固定大小或者可動態擴展的內存大小。
Java虛擬機使用局部變量表來完成方法調用時的參數傳遞。局部變量表的長度在編譯期已經決定了並存儲於類和接口的二進制表示中,一個局部變量能夠保存一個類型爲boolean、byte、char、short、float、reference和returnAddress的數據,兩個局部變量能夠保存一個類型爲long和double的數據。
  Java虛擬機提供一些字節碼指令來從局部變量表或者對象實例的字段中複製常量或變量值到操做數棧中,也提供了一些指令用於從操做數棧取走數據、操做數據和把操做結果從新入棧。在方法調用的時候,操做數棧也用來準備調用方法的參數以及接收方法返回結果。
  每一個棧幀中都包含一個指向運行時常量區的引用支持當前方法的動態連接。在Class文件中,方法調用和訪問成員變量都是經過符號引用來表示的,動態連接的做用就是將符號引用轉化爲實際方法的直接引用或者訪問變量的運行是內存位置的正確偏移量。
總的來講,Java虛擬機棧是用來存放局部變量和過程結果的地方。
Java虛擬機棧可能發生以下異常狀況: 若是Java虛擬機棧被實現爲固定大小內存,線程請求分配的棧容量超過Java虛擬機棧容許的最大容量時,Java虛擬機將會拋出一個StackOverflowError異常。
若是Java虛擬機棧被實現爲動態擴展內存大小,而且擴展的動做已經嘗試過,可是目前沒法申請到足夠的內存去完成擴展,或者在創建新的線程時沒有足夠的內存去建立對應的虛擬機棧,那Java虛擬機將會拋出一個OutOfMemoryError異常。

1.符號引用(Symbolic References):

  符號引用以一組符號來描述所引用的目標,符號能夠是任何形式的字面量,只要使用時可以無歧義的定位到目標便可。例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等類型的常量出現。符號引用與虛擬機的內存佈局無關,引用的目標並不必定加載到內存中。在Java中,一個java類將會編譯成一個class文件。在編譯時,java類並不知道所引用的類的實際地址,所以只能使用符號引用來代替。好比org.simple.People類引用了org.simple.Language類,在編譯時People類並不知道Language類的實際內存地址,所以只能使用符號org.simple.Language(假設是這個,固然實際中是由相似於CONSTANT_Class_info的常量來表示的)來表示Language類的地址。各類虛擬機實現的內存佈局可能有所不一樣,可是它們能接受的符號引用都是一致的,由於符號引用的字面量形式明肯定義在Java虛擬機規範的Class文件格式中。

2.直接引用:

直接引用能夠是

(1)直接指向目標的指針(好比,指向「類型」【Class對象】、類變量、類方法的直接引用多是指向方法區的指針)

(2)相對偏移量(好比,指向實例變量、實例方法的直接引用都是偏移量)

(3)一個能間接定位到目標的句柄

直接引用是和虛擬機的佈局相關的,同一個符號引用在不一樣的虛擬機實例上翻譯出來的直接引用通常不會相同。若是有了直接引用,那引用的目標一定已經被加載入內存中了。

4,本地方法棧

對於一個運行中的Java程序而言,它還可能會用到一些跟本地方法相關的數據區。當某個線程調用一個本地方法時,它就進入了一個全新的而且再也不受虛擬機限制的世界。本地方法能夠經過本地方法接口來訪問虛擬機的運行時數據區,但不止如此,它還能夠作任何它想作的事情。

  本地方法本質上時依賴於實現的,虛擬機實現的設計者們能夠自由地決定使用怎樣的機制來讓Java程序調用本地方法。

  任何本地方法接口都會使用某種本地方法棧。當線程調用Java方法時,虛擬機會建立一個新的棧幀並壓入Java棧。然而當它調用的是本地方法時,虛擬機會保持Java棧不變,再也不在線程的Java棧中壓入新的幀,虛擬機只是簡單地動態鏈接並直接調用指定的本地方法。

  若是某個虛擬機實現的本地方法接口是使用C鏈接模型的話,那麼它的本地方法棧就是C棧。當C程序調用一個C函數時,其棧操做都是肯定的。傳遞給該函數的參數以某個肯定的順序壓入棧,它的返回值也以肯定的方式傳回調用者。一樣,這就是虛擬機實現中本地方法棧的行爲。

  極可能本地方法接口須要回調Java虛擬機中的Java方法,在這種狀況下,該線程會保存本地方法棧的狀態並進入到另外一個Java棧。

  下圖描繪了這樣一個情景,就是當一個線程調用一個本地方法時,本地方法又回調虛擬機中的另外一個Java方法。

  這幅圖展現了JAVA虛擬機內部線程運行的全景圖。一個線程可能在整個生命週期中都執行Java方法,操做它的Java棧;或者它可能毫無障礙地在Java棧和本地方法棧之間跳轉。 

該線程首先調用了兩個Java方法,而第二個Java方法又調用了一個本地方法,這樣致使虛擬機使用了一個本地方法棧。假設這是一個C語言棧,其間有兩個C函數,第一個C函數被第二個Java方法當作本地方法調用,而這個C函數又調用了第二個C函數。以後第二個C函數又經過本地方法接口回調了一個Java方法(第三個Java方法),最終這個Java方法又調用了一個Java方法(它成爲圖中的當前方法)。

Navtive 方法是 Java 經過 JNI 直接調用本地 C/C++ 庫,能夠認爲是 Native 方法至關於 C/C++ 暴露給 Java 的一個接口,Java 經過調用這個接口從而調用到 C/C++ 方法。當線程調用 Java 方法時,虛擬機會建立一個棧幀並壓入 Java 虛擬機棧。然而當它調用的是 native 方法時,虛擬機會保持 Java 虛擬機棧不變,也不會向 Java 虛擬機棧中壓入新的棧幀,虛擬機只是簡單地動態鏈接並直接調用指定的 native 方法。

5,程序計數器

程序計數器是一個記錄着當前線程所執行的字節碼的行號指示器。

  JAVA代碼編譯後的字節碼在未通過JIT(實時編譯器)編譯前,其執行方式是經過「字節碼解釋器」進行解釋執行。簡單的工做原理爲解釋器讀取裝載入內存的字節碼,按照順序讀取字節碼指令。讀取一個指令後,將該指令「翻譯」成固定的操做,並根據這些操做進行分支、循環、跳轉等流程。

  從上面的描述中,可能會產生程序計數器是不是多餘的疑問。由於沿着指令的順序執行下去,即便是分支跳轉這樣的流程,跳轉到指定的指令處按順序繼續執行是徹底可以保證程序的執行順序的。假設程序永遠只有一個線程,這個疑問沒有任何問題,也就是說並不須要程序計數器。但實際上程序是經過多個線程協同合做執行的。

  首先咱們要搞清楚JVM的多線程實現方式。JVM的多線程是經過CPU時間片輪轉(即線程輪流切換並分配處理器執行時間)算法來實現的。也就是說,某個線程在執行過程當中可能會由於時間片耗盡而被掛起,而另外一個線程獲取到時間片開始執行。當被掛起的線程從新獲取到時間片的時候,它要想從被掛起的地方繼續執行,就必須知道它上次執行到哪一個位置,在JVM中,經過程序計數器來記錄某個線程的字節碼執行位置。所以,程序計數器是具有線程隔離的特性,也就是說,每一個線程工做時都有屬於本身的獨立計數器。

程序計數器的特色

  1.線程隔離性,每一個線程工做時都有屬於本身的獨立計數器。
  2.執行java方法時,程序計數器是有值的,且記錄的是正在執行的字節碼指令的地址(參考上一小節的描述)。
  3.執行native本地方法時,程序計數器的值爲空(Undefined)。由於native方法是java經過JNI直接調用本地C/C++庫,能夠近似的認爲native方法至關於C/C++暴露給java的一個接口,java經過調用這個接口從而調用到C/C++方法。因爲該方法是經過C/C++而不是java進行實現。那麼天然沒法產生相應的字節碼,而且C/C++執行時的內存分配是由本身語言決定的,而不是由JVM決定的。

​ 4.程序計數器佔用內存很小,在進行JVM內存計算時,能夠忽略不計。

  5.程序計數器,是惟一一個在java虛擬機規範中沒有規定任何OutOfMemoryError的區域。

6,線程棧

線程堆棧也稱線程調用堆棧,是虛擬機中線程(包括鎖)狀態的一個瞬間狀態的快照,即系統在某一個時刻全部線程的運行狀態,包括每個線程的調用堆棧,鎖的持有狀況。雖然不一樣的虛擬機打印出來的格式有些不一樣,可是線程堆棧的信息都包含:

一、線程名字,id,線程的數量等。

二、線程的運行狀態,鎖的狀態(鎖被哪一個線程持有,哪一個線程在等待鎖等)

三、調用堆棧(即函數的調用層次關係)調用堆棧包含完整的類名,所執行的方法,源代碼的行數。

由於線程棧是瞬時快照包含線程狀態以及調用關係,因此藉助堆棧信息能夠幫助分析不少問題,好比線程死鎖,鎖爭用,死循環,識別耗時操做等等。線程棧是瞬時記錄,因此沒有歷史消息的回溯,通常咱們都須要結合程序的日誌進行跟蹤,通常線程棧能分析以下性能問題:

一、系統平白無故的cpu太高

二、系統掛起,無響應

三、系統運行愈來愈慢

四、性能瓶頸(如沒法充分利用cpu等)

五、線程死鎖,死循環等

六、因爲線程數量太多致使的內存溢出(如沒法建立線程等)

線程棧狀態

線程棧狀態有以下幾種

一、NEW

二、RUNNABLE

三、BLOCKED

四、WAITING

五、TIMED_WAITING

六、TERMINATED

下面依次對6種線程棧狀態進行介紹。

一、NEW

線程剛剛被建立,也就是已經new過了,可是尚未調用start()方法,這個狀態咱們使用jstack進行線程棧dump的時候基本看不到,由於是線程剛建立時候的狀態。

二、RUNNABLE

從虛擬機的角度看,線程正在運行狀態,狀態是線程正在正常運行中, 固然可能會有某種耗時計算/IO等待的操做/CPU時間片切換等, 這個狀態下發生的等待通常是其餘系統資源, 而不是鎖, Sleep等。

處於RUNNABLE狀態的線程是否是必定會消耗cpu呢,不必定,像socket IO操做,線程正在從網絡上讀取數據,儘管線程狀態RUNNABLE,但實際上網絡io,線程絕大多數時間是被掛起的,只有當數據到達後,線程纔會被喚起,掛起發生在本地代碼(native)中,虛擬機根本不一致,不像顯式的調用sleep和wait方法,虛擬機才能知道線程的真正狀態,但在本地代碼中的掛起,虛擬機沒法知道真正的線程狀態,所以一律顯示爲RUNNABLE。

三、BLOCKED

線程處於阻塞狀態,正在等待一個monitor lock。一般狀況下,是由於本線程與其餘線程公用了一個鎖。其餘在線程正在使用這個鎖進入某個synchronized同步方法塊或者方法,而本線程進入這個同步代碼塊也須要這個鎖,最終致使本線程處於阻塞狀態。

真實生活例子:

今天你要去阿里面試。這是你夢想的工做,你已經盯着它多年了。你早上起來,準備好,穿上你最好的外衣,對着鏡子打理好。當你走進車庫發現你的朋友已經把車開走了。在這個場景,你只有一輛車,因此怎麼辦?在真實生活中,可能會打架搶車。 如今由於你朋友把車開走了你被BLOCKED了。你不能去參加面試。

這就是BLOCKED狀態。用技術術語講,你是線程T1,你朋友是線程T2,而鎖是車。T1BLOCKED在鎖(例子裏的車)上,由於T2已經獲取了這個鎖。

四、WAITING

這個狀態下是指線程擁有了某個鎖以後, 調用了他的wait方法, 等待其餘線程/鎖擁有者調用 notify / notifyAll一遍該線程能夠繼續下一步操做, 這裏要區分 BLOCKED 和 WATING 的區別, 一個是在臨界點外面等待進入, 一個是在理解點裏面wait等待別人notify, 線程調用了join方法 join了另外的線程的時候, 也會進入WAITING狀態, 等待被他join的線程執行結束,處於waiting狀態的線程基本不消耗CPU。

真實生活例子:

再看下幾分鐘後你的朋友開車回家了,鎖(車)就被釋放了,如今你意識到快到面試時間了,而開車過去很遠。因此你拼命地踩油門。限速120KM/H而你以160KM/H的速度在開。很不幸,一個交警發現你超速了,讓你停到路邊。如今你進入了WAITING狀態。你停下車坐在那等着交警過來檢查開罰單而後給你放行。基本上,你只有等他讓你走(你無法開車逃),你被卡在WAITING狀態了。

用技術術語來說,你是線程T1而交警是線程T2。你釋放你的鎖(例子中你停下了車),並進入WAITING狀態,直到警察(例子中T2)讓你走,你陷入了WAITING狀態。

五、TIMED_WAITING

該線程正在等待,經過使用了 sleep, wait, join 或者是 park 方法。(這個與 WAITING 不一樣是經過方法參數指定了最大等待時間,WAITING 能夠經過時間或者是外部的變化解除),線程等待指定的時間。

真實生活例子:

儘管此次面試過程充滿戲劇性,但你在面試中作的很是好,驚豔了全部人並得到了高薪工做。你回家告訴你的鄰居你的新工做並表達你激動的心情。你的朋友告訴你他也在同一個辦公樓裏工做。他建議你坐他的車去上班。你想這不錯。因此去阿里上班的第一天,你走到你鄰居的房子,在他的房子前停好你的車。你等了他10分鐘,但你的鄰居沒有出現。你而後繼續開本身的車去上班,這樣你不會在第一天就遲到。這就是TIMED_WAITING.

用技術術語來解釋,你是線程T1而你的鄰居是線程T2。你釋放了鎖(這裏是中止開車)並等了足足10分鐘。若是你的鄰居T2沒有來,你繼續開車(老司機注意車速,其餘乘客記得買票)。

六、TERMINATED

線程終止,一樣咱們在使用jstack進行線程dump的時候也不多看到該狀態的線程棧。

1.局部變量表

局部變量表(Local Variable Table)是一組變量值存儲空間,用於存放方法參數和方法內定義的局部變量。局部變量表的容量以變量槽(Variable Slot)爲最小單位,Java虛擬機規範並無定義一個槽所應該佔用內存空間的大小,可是規定了一個槽應該能夠存放一個32位之內的數據類型。

在Java程序編譯爲Class文件時,就在方法的Code屬性中的max_locals數據項中肯定了該方法所需分配的局部變量表的最大容量。(最大Slot數量)

一個局部變量能夠保存一個類型爲boolean、byte、char、short、int、float、reference和returnAddress類型的數據。reference類型表示對一個對象實例的引用。returnAddress類型是爲jsr、jsr_w和ret指令服務的,目前已經不多使用了。

虛擬機經過索引定位的方法查找相應的局部變量,索引的範圍是從0~局部變量表最大容量。若是Slot是32位的,則遇到一個64位數據類型的變量(如long或double型),則會連續使用兩個連續的Slot來存儲。

2.操做數棧

操做數棧(Operand Stack)也常稱爲操做棧,它是一個後入先出棧(LIFO)。同局部變量表同樣,操做數棧的最大深度也在編譯的時候寫入到方法的Code屬性的max_stacks數據項中。

操做數棧的每個元素能夠是任意Java數據類型,32位的數據類型佔一個棧容量,64位的數據類型佔2個棧容量,且在方法執行的任意時刻,操做數棧的深度都不會超過max_stacks中設置的最大值。

當一個方法剛剛開始執行時,其操做數棧是空的,隨着方法執行和字節碼指令的執行,會從局部變量表或對象實例的字段中複製常量或變量寫入到操做數棧,再隨着計算的進行將棧中元素出棧到局部變量表或者返回給方法調用者,也就是出棧/入棧操做。一個完整的方法執行期間每每包含多個這樣出棧/入棧的過程。

3.動態鏈接

在一個class文件中,一個方法要調用其餘方法,須要將這些方法的符號引用轉化爲其在內存地址中的直接引用,而符號引用存在於方法區中的運行時常量池。

Java虛擬機棧中,每一個棧幀都包含一個指向運行時常量池中該棧所屬方法的符號引用,持有這個引用的目的是爲了支持方法調用過程當中的動態鏈接(Dynamic Linking)

這些符號引用一部分會在類加載階段或者第一次使用時就直接轉化爲直接引用,這類轉化稱爲靜態解析。另外一部分將在每次運行期間轉化爲直接引用,這類轉化稱爲動態鏈接。

4.靜態連接

靜態連接的過程就已經把要連接的內容已經連接到了生成的可執行文件中,就算你在去把靜態庫刪除也不會影響可執行程序的執行;而動態連接這個過程卻沒有把內容連接進去,而是在執行的過程當中,再去找要連接的內容,生成的可執行文件中並無要連接的內容,因此當你刪除動態庫時,可執行程序就不能運行。

通俗解釋:靜態鏈接庫就是把(lib)文件中用到的函數代碼直接連接進目標程序,程序運行的時候再也不須要其它的庫文件;動態連接就是把調用的函數所在文件模塊(DLL)和調用函數在文件中的位置等信息連接進目標程序,程序運行的時候再從DLL中尋找相應函數代碼,所以須要相應DLL文件的支持。

這篇內容主要介紹一下圖中的概念。下篇文章我會把這些概念串起來,好比說建立對象的過程,內存空間是怎麼工做的。感謝你們的持續關注。

另外我在個人公衆號內,針對JVM寫了一個系列介紹內容,想要獲取更多內容,請關注公衆號:奇客時間

相關文章
相關標籤/搜索