java gc堆中的分区_jvm内存各个区域详解
內存區域劃分
Java虛擬機所管理的內存區域分為如下部分:方法區、GC堆、虛擬機棧、本地方法棧、PC程序計數器。
其中方法區、GC堆是所有線程共享的;虛擬機棧、本地方法棧、PC程序計數器是各個線程獨占的。
image.png
PC程序計數器
程序計數器可以看做是當前線程所執行的字節碼的行號指示器。字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令。
Java虛擬機的多線程是通過線程輪流切換并分配處理器執行時間的方式來實現的。每條線程都一個獨立的程序計數器,各條線程之間的計數器互不影響,獨立存儲,我們稱這類內存區域為“線程私有”的內存。
如果線程正值執行的是一個java方法,這個計數器記錄的是正在執行的的虛擬機字節碼指令的地址;如果正在執行的是native方法,這個計數器則為空(undefined)。
此內存區域是唯一一個在java虛擬機規范中沒有規定任何OutOfMemoryError情況的區域。
java虛擬機棧
與程序計數器一樣,java虛擬機棧也是線程私有的,它的生命周期和線程相同。
虛擬機棧描述的是java方法執行的內存模型:每個方法被執行的時候都會同時創建一個棧幀 用于存儲局部變量表、操作棧、動態鏈接、方法出口等信息。每一個方法被調用直至執行完成的過程,就對應著一個棧幀在虛擬機棧中從入棧到出棧的過程。
下面我們詳細介紹下虛擬機棧的棧幀,包括局部變量表、操作數棧、動態鏈接、方法出口四部分。
image.png
符號引用和直接引用
符號引用即用用字符串符號的形式來表示引用,其實被引用的類、方法或者變量還沒有被加載到內存中。而直接引用則是有具體引用地址的指針,被引用的類、方法或者變量已經被加載到內存中。
符號引用要轉換成直接引用才有效,這也說明直接引用的效率要比符號引用高。那為什么要用符號引用呢?這是因為類加載之前,javac會將源代碼編譯成.class文件,這個時候javac是不知道被編譯的類中所引用的類、方法或者變量他們的引用地址在哪里,所以只能用符號引用來表示。
我們都知道,類加載過程分為加載—>驗證—>準備—>解析—>初始化這 5個階段,符號引用轉換為直接引用就發生在解析階段,解析階段可能在初始化前,也可能在初始化之后.
局部變量表
局部變量表(Local Variable Table)是一組變量值存儲空間,用于存放方法參數和方法內部定義的局部變量。
在編譯為Class文件時就在方法的code屬性的max_locals數據項中確定了該方法所需要分配的局部變量表的最大容量
一個Slot變量槽可以存放一個32位以內的數據類型,Java中占用32位以內的數據類型有boolean、byte、char、short、float、reference和returnAddress8種類型。第7種reference類表示對一個對象實例的引用。
如果是實例方法(非static的方法),那么局部變量表中第0位索引的Slot默認是用于傳遞方法所屬對象實例的引用"this"。其余參數則按照參數表的順序來排列。比如方法method(int a1,inta2),局部變量表索引0、1、2則分別存儲了this指針、a1、a2,如果方法內部有其他內部變量,則在局部變量表中存在a2之后的位置。
為了盡可能節省棧幀空間,局部變量表中的Slot是可以重用的,方法體中定義的變量,其作用域并不一定會覆蓋整個方法體,如果當前字節碼PC計數器的值已經超出了某個變量的作用域,那這個變量對應的Slot就可以交給其他變量使用。
局部變量不像的類成員變量那樣存在"準備階段"。我們知道類變量有兩次賦初始值的過程,一次在準備階段,賦予系統初始值;另外一次在初始化階段,賦予程序員定義的初始值。因此,即使在初始化階段程序員沒有為類變量賦值也沒有關系,類變量仍然具有一個確定的初始值。但局部變量就不一樣,如果一個局部變量定義了但沒有賦初始值是不能使用的,不要認為Java中任何情況下都存在諸如整型變量默認為0,布爾型變量默認為false等這樣的默認值。
image.png
操作數棧
操作數棧(Operand Stack)也常稱為操作棧,它是一個后人先出(Last In First out,LIFO)棧。
同局部變量表一樣,操作數棧的最大深度也在編譯的時候寫人到code屬性的max_stacks數據項中。
當一個方法剛剛開始執行的時候,這個方法的操作數棧是空的,在方法的執行過程中,會有各種字節碼指令往操作數棧中寫人和提取內容,也就是出棧/入棧操作。例如,在做算術運算的時候是通過操作數棧來進行的,又或者在調用其他方法的時候是通過操作數棧來進行參數傳遞的。舉個例子,整數加法的字節碼指令iadd在運行的時候操作數棧中最接近棧頂的兩個元素已經存人了兩個int型的數值,當執行這個指令時,會將這兩個int值出棧并相加,然后將相加的結果入棧
Java虛擬機的解釋執行引擎稱為“基于棧的執行引擎”,其中所指的“棧”就是操作數棧。如果當前線程請求的棧深度大于虛擬機所允許的最大深度,將拋出StackOverflowError異常。
動態鏈接
每個棧幀都包含一個指向運行常量池中該棧幀所屬方法的引用,持有這個引用是為了支持方法調用過程中的動態連接。
Class文件的常量池中存有大量的符號引用,字節碼中的方法調用指令就以常量池中指向方法的符號引用作為參數。這些符號引用一部分會在類加載階段或者第一次使用的時候就轉化為直接引用,這種轉化稱為靜態解析。另外一部分將在每一次運行期間轉化為直接引用,這部分稱為動態連接。
Java代碼在進行Javac編譯的時候,并不像C和C++那樣有“連接”這一步驟,而是在虛擬機加載Class文件的時候進行動態連接。也就是說,在Class文件中不會保存各個方法、字段的最終內存布局信息,因此這些字段、方法的符號引用不經過運行期轉換的話無法得到真正的內存人口地址,也就無法直接被虛擬機使用。當虛擬機運行時,需要從常量池獲得對應的符號引用,再在類創建時或運行時解析、翻譯到具體的內存地址之中。
方法的返回地址
當一個方法開始執行后,只有兩種方式可以退出這個方法。第一種方式是執行引擎遇任意一個方法返回的字節碼指令,這時候可能會有返回值傳遞給上層的方法調用者,這種退出方法的方式稱為正常完成出口。
另外一種退出方式是,在方法執行過程中遇到了異常,并且這個異常沒有在方法體內得到處理,在本方法的異常表中沒有搜索到匹配的異常處理器,就會導致方法退出,這種退出方法的方式稱為異常完成出口。一個方法使用異常完成出口的方式退出,是不會給它的上層調用者產生任何返回值的。
在方法退出之后,需要返回到方法被調用的位置,方法返回時可能需要在棧幀中保存一些信息,用來幫助恢復它的上層方法的執行狀態。一般來說,方法正常退出時,調用者的PC計數器的值可以作為返回地址,棧幀中很可能會保存這個計數器值。
而方法異常退出時,返回地址是要通過異常處理器表來確定的,棧幀中一般不會保存這部分信息。方法退出的過程實際上就等同于把當前棧幀出棧,因此退出時可能執行的操作有:恢復上層方法的局部變量表和操作數棧,把返回值(如果有的話)壓人調用者棧幀的操作數棧中,調整pc計數器的值以指向方法調用指令后面的一條指令等。
本地方法棧
本地方法棧是一個后入先出(Last In First Out)棧.
本地方法棧與 Java 虛擬機棧所發揮的作用是非常相似的,其區別不過是虛擬機棧為虛擬機執行 Java 方法(也就是字節碼)服務,而本地方法棧則是為虛擬機使用到的 Native 方法服務。
Navtive 方法是 Java 通過 JNI 直接調用本地 C/C++ 庫,可以認為是 Native 方法相當于 C/C++ 暴露給 Java 的一個接口,Java 通過調用這個接口從而調用到 C/C++ 方法。當線程調用 Java 方法時,虛擬機會創建一個棧幀并壓入 Java 虛擬機棧。然而當它調用的是 native 方法時,虛擬機會保持 Java 虛擬機棧不變,也不會向 Java 虛擬機棧中壓入新的棧幀,虛擬機只是簡單地動態連接并直接調用指定的 native 方法。
image.png
方法區
方法區與java堆一樣,是各個線程共享的內存區域,它用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。
垃圾收集行為在這個區域是比較少出現的,這個區域的內存回收目標主要是針對常量池的回收和對類型的卸載。
方法區存儲的類型信息包括(這個類型的完整有效名、這個類型直接父類的完整有效名、這個類型直接接口的一個有序列表、這個類型的修飾符;類型的常量池、域(Field)信息、方法(Method)信息、除了常量外的所有靜態(static)變量)
類型的常量池
jvm為每個已加載的類型都維護一個常量池。常量池就是這個類型用到的常量的一個有序集合,包括實際的常量(string, integer, 和floating point常量)和對類型,域和方法的符號引用。池中的數據項象數組項一樣,是通過索引訪問的。
因為常量池存儲了一個類型所使用到的所有類型,域和方法的符號引用,所以在java程序的動態鏈接中起了核心的作用
運行時的常量池
運行時的常量池是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述等消息外,還有一項信息是常量池,用于存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載后存放到方法區的運行時的常量池中。一般來說,除了保存Class文件中描述的符號引用外,還會把翻譯出來的直接引用也存儲在運行時常量池中。
運行時常量池相對于Class文件常量池的另外一個重要特征就是具備動態性。Java語言并不要求常量一定只能在編譯期產生,也就是并非預置入Class文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中。
GC堆
Java堆是java虛擬機所管理內存中最大的一塊。Java堆是被所有線程所共享的一塊內存區域,在虛擬機啟動的時候創建。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這里分配內存。
Java堆是垃圾回收器管理的主要區域,因此很多時候也被稱為“GC堆”。Java堆可以處于物理上不連續的內存空間中,只要邏輯上是連續的即可。
總結
以上是生活随笔為你收集整理的java gc堆中的分区_jvm内存各个区域详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: qt中生成含有中文的json文件,读取含
- 下一篇: 求qt如何解决小数计算过程中的精度丢失