java運行時數據區域的劃分與知識點(java面試必備)

運行時數據區域

java虛擬機在執行java程序的過程中會把它所管理的內存劃分爲若干個不同的數據區域
下圖是jdk8後的JVM內存佈局,引用於https://www.cnblogs.com/czwbig/p/11127124.html
在這裏插入圖片描述
jdk8之前的JVM內存佈局,來源如上
在這裏插入圖片描述

1.1 程序計數器

Program Counter Register

程序計數器存儲的值分爲兩種情況:

  1. 執行java的方法時,程序計數器存儲下一條需要執行的字節碼指令的地址
  2. 執行native方法時,程序計數器爲空(undefined),native方法是java調用非java實現的代碼,這部分的代碼java只負責調用,無法生成字節碼交給jvm,native方法的執行過程和內存管理由jvm所在的原生平臺負責,jvm不負責

下圖對java方法和native方法的內存管理做了區分
在這裏插入圖片描述

每一個線程都有一個程序計數器,方便線程切換後能恢復到上一次運行的位置.
jvm加載class文件時已經知道了程序計數器的最大偏移量,所以程序計數器不會拋出
OutOfMemoryError異常(可以理解爲程序計數器在class中的字節碼中左右反覆橫跳,但不會跳出已知的區域)

1.2 java虛擬機棧

Java Virtual Machine Stacks
java虛擬機棧是線程私有的,生命週期與線程相同。
虛擬機棧描述的是java方法執行的內存模型,每個方法在開始執行前會創建一個棧幀(Stack Frame),並推入虛擬機棧,方法執行結束後則出棧,可以把棧幀理解爲一個java方法運行時在內存中的一個實體映射。
java虛擬機規範中規定了java虛擬機棧的兩種異常:線程請求的棧深度大於虛擬機允許的深度(棧幀太多,即方法層級調用過深),會拋出StackoverflowError;若虛擬機棧允許動態擴展,但無法申請到足夠的內存,拋出OutOfMemoryError
棧幀用於存儲局部變量表、操作數棧、動態鏈接、方法返回地址(returnAddress類型,指向了一條字節碼指令的地址)、附加信息等信息。這裏可以跳到書的第八章瞭解棧幀的結構
在這裏插入圖片描述

棧幀的各個信息描述如下:

  1. 局部變量表
    局部變量表是一組變量值存儲空間,用於存放方法參數和方法內部定義的局部變量。在Java程序編譯爲Class文件時,就在方法表的Code屬性的max_locals數據項中確定了該方法需要分配的最大局部變量表的容量。
    局部變量表的容量以變量槽(Variable Slot)爲最小單位,slot的大小根據操作系統、處理器、JVM的不同有所差別,虛擬機規範中說每個slot都應該能存放一個boolean、byte、char、short、int、float、reference或returnAddress類型的數據
    虛擬機會以高位對其的方式爲64位的數據類型分配兩個連續的slot,不允許單獨訪問兩個slot的任意一個,否則會拋出異常
    按照索引訪問局部變量表,對於實例方法,index=0的slot默認是方法所屬對象實例的引用(this)
    slot可以複用,方法中的變量的作用域不一定是整個方法,在離開變量作用域後,對應的slot可以給其他變量使用。
    slot複用可能帶來的內存回收問題見另外的文章https://blog.csdn.net/qq_36267931/article/details/106822588

    局部變量如果不初始化則無法使用。

  2. 操作數棧
    Operand Stack
    方法剛開始執行時,操作數棧是空的,方法執行過程中,會有各種字節碼指令往操作數棧中寫入和提取內容,執行入棧/出棧操作(學過計組的應該對這個很好理解)
    操作數棧的最大深度在編譯時寫入到Code屬性的max_stack數據項中,操作數棧的每個元素可以是任意的java數據類型,包括long double

  3. 動態鏈接
    每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程中的動態鏈接
    關於靜態分派與動態分派可以參考https://blog.csdn.net/sunxianghuang/article/details/52280002

  4. 方法返回地址
    方法執行結束有兩種方法,一種是正常返回,如果有返回值則把返回值傳給上層方法,另一種是發生異常,而且方法中沒有對應的異常處理器,此時不會有返回值。
    正常返回的情況下,方法返回地址存儲的是方法執行結束後程序要執行的下一條字節碼指令的地址,一般是方法調用者的PC計數器的值
    異常返回的情況下,返回地址通過異常處理器來確定,棧幀中一般不會保存這部分信息

  5. 附加信息

    附加信息裏是一些規範裏沒有描述的信息,取決於具體的虛擬機實現

1.3 本地方法棧

Native Method Stack
本地方法棧與虛擬機棧的作用非常類似,虛擬機棧爲虛擬機執行java方法,本地方法棧爲虛擬機使用到的本地方法服務,本地方法與java 方法的差別可以見 1.1小節的圖
本地方法棧也會拋出StackoverflowError、OutOfMemoryError

1.4 java堆

Java Heap
java堆被所有線程共享,用於存放大部分對象實例。
Java堆是垃圾收集器管理的主要區域,所以也叫GC堆。
從內存回收算法來看,堆可以分爲新生代和老年代,從內存分配角度看,線程共享的java堆可能劃出多個線程私有的分配緩衝區(Thread Local Allocation Buffer,TLAB)
java堆相關的知識後續再寫文章補充

1.5 方法區

Method Area
jdk8之前纔有的內存區域,用於存儲已被虛擬機啊加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據,是線程共享的區域
使用HotSpot虛擬機的開發者,更願意把方法區成爲永久代(Permanent Generation)。因爲HotSpot的設計團隊用永久代來實現方法區,使得垃圾收集器可以像管理java堆一樣管理方法區,但這樣的實現由性能和內存溢出的問題,且其他虛擬機不存在永久代的說法,所以在jdk8後不再有方法區

JDK8 開始使用元空間(Metaspace),以前永久代所有內容的字符串常量移至堆內存,其他內容移至元空間,元空間直接在本地內存分配。爲什麼要使用元空間取代永久代的實現?

  1. 字符串存在永久代中,容易出現性能問題和內存溢出。
  2. 類及方法的信息等比較難確定其大小,因此對於永久代的大小指定比較困難,太小容易出現永久代溢出,太大則容易導致老年代溢出。
  3. 永久代會爲 GC 帶來不必要的複雜度,並且回收效率偏低。
  4. 將 HotSpot 與 JRockit 合二爲一。

1.6 運行時常量池

運行時常量池(Runtime Constant Pool)是方法區的一部分。Class 文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池(Constant Pool Table),用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載後進入方法區的運行時常量池中存放。一般來說,除了保存 Class 文件中描述的符號引用外,還會把翻譯出來的直接引用也存儲在運行時常量池中。運行時常量池相對於 Class 文件常量池的另外一個重要特徵是具備動態性,Java 語言並不要求常量一定只有編譯期才能產生,也就是並非預置入 Class 文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用得比較多的便是 String 類的 intern() 方法。既然運行時常量池是方法區的一部分,自然受到方法區內存的限制,當常量池無法再申請到內存時會拋出 OutOfMemoryError 異常。

1.7 直接內存

Direct Memory
直接內存不是虛擬機運行時數據區的一部分,也不是虛擬機規範中定義的內存區域。
JDk1.4後加入了NIO類,它可以直接使用Native方法分配堆外內存(直接內存),然後通過存儲在java堆中的DirectByteBuffer對象對直接內存進行操作,使用直接內存可以避免數據在java堆和堆外內存中來回切換,進而提高性能。
關於直接內存可以參考https://www.jianshu.com/p/007052ee3773

參考

《深入理解Java虛擬機:JVM高級特性與最佳實踐(第二版)》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章