1. 程式人生 > >JVM學習-之對象的創建和內存分配

JVM學習-之對象的創建和內存分配

point IV www 方法區 包括 軌跡 from con 指定

最近看JVM內存模型,看了很多文章,大都講到JVM將內存區域劃分分:Mehtod-Area(No heap) 方法區,Heap(堆)區,Program Counter Register(程序計數器),VM Stack(虛擬機棧),Native Mehtod Stack(本地方法棧),其中方法區和堆區是線程共享的。而虛擬機棧,本地方法棧,程序計數器是非線程共享的。每個java程序在自己的虛擬機上,然後告知虛擬機程序的運行入口。再被虛擬機字節碼解釋器加載運行。JVM運行的時候都會 分配好方法區和堆區,每遇到一個線程則分配程序計數器,虛擬機棧,本地方法棧。當線程運行結束時,則程序計數器,虛擬機棧,本地方法棧的內存空間也會被釋放掉。這也就是為什麽把內存區域劃分分線程共享和非共享的原因,非線程共享的那三個區域其生命周期和所屬的線程相同,隨線程的結束而結束。而線程共享的區域和JAVA程序運行的生命周期相同。這也是垃圾回收總是發生在線程共享區域的原因。接著就是把各個區域的作業以及運行時存儲類的那些數據做了分類概述:

先引入一張借鑒的圖片,有個清晰的輪廓:

技術分享圖片

1. 程序計數器:

程序計數器用於保存當前正在執行的程序的內存地。JAVA中程序的分支,跳轉,循環,異常處理和多線程環境中線程的恢復,都依賴於這個功能。因為程序執行的軌跡不可能一直是線性執行的,當有多個線程交叉執行時,被中斷的線程的程序當前執行到那一條內存地址必然要記錄下來。以便被中斷的線程得到恢復執行的機會時可以繼續按中斷時的指令執行下去。所以每個線程都有一個獨立的程序計數器,各個線程之間的計數器互不影響,獨立存儲,屬於線程所私有的。

2. JVM 棧:

虛擬機棧,也叫棧內存,是線程創建時創建,它的生命周期是跟隨線程的生命周期,線程結束棧內存也就釋放了。JAVA棧總是線程關聯到一起,每當創建一個線程,JVM就會為該線程創建對應的棧,這個JAVA棧中有包含有多個棧幀。這個棧幀是和每個方法關聯的,每運行一個方法就創建一個棧幀。每個棧幀包含局部變量表(包含了對應的方法參數和局部變量),操作棧(Operand Stack,記錄出棧、入棧的操作),動態鏈接和方法出口等信息。每個方法被調用直到執行完畢的過程,對應這棧幀在虛擬棧的入棧和出棧的過程。由於JVM 棧和線程關聯起來的,Java棧數據不是線程共享的,所以不用關心其數據的一致性問題,也不會存在同步鎖的問題。但在Java虛擬機規範中,對這個區域規定了兩種異常狀況:如果線程請求的棧深度大於虛擬機所允許的深度,將拋出StackOverflowError異常;如果虛擬機可以動態擴展,如果擴展時無法申請到足夠的內存,就會拋出OutOfMemoryError異常。在Hot Spot虛擬機中,可以使用-Xss參數來設置棧的大小。棧的大小直接決定了函數調用的可達深度。

3. Heap 堆:

堆,一種數據結構,FIFO,先進先出。堆也是是JVM管理內存中最大的一塊,是被所有JAVA線程所共享的,是非線程安全的。在JVM啟動時創建,專門用來保存對的實例。例:new Person();出來的對象都存放在堆中,還有數組對象。實際上也只是保存對象實例的屬性值,屬性的類型和對象本身的類型,並不保存對象的方法(已幀的形式保存在棧中),在堆中分配一定的內存保存對象的實例。對象實例在堆中分配好以後,需要在棧中保存4個字節的heap內存地址,用來定位對象實例在堆中的位置,便宜找到該對象。所以,JAVA堆區也是GC主要工作的場所。從內存回收的角度來看,由於現在的GC都采用分代回收的算法,所以java堆還可以細分為,新生代,老年代;新生代再細分為Eden空間,From survivor,To survivor空間等。

4. Method Area 方法區:

方法區主要存放了加載類定義的數據(名稱,修飾符等),類中的靜態常量,類中定義為final類型的常量,類中的Field信息,類中的方法信息,當在程序中通過Class對象的getName.isInterface等方法獲取信息時,這些數據都來自方法區。java方法區是所有線程所共享的。不像Java堆中其他部分一樣會頻繁被GC回收,它存儲的信息相對比較穩定,在一定條件下會被GC,當方法區要使用的內存超過其允許的大小時,會拋出OutOfMemory的錯誤信息。方法區也是堆中的一部分(正因為方法區所存儲的數據與堆有一種類比關系,所以它還被稱為 Non-Heap),就是我們通常所說的Java堆中的永久區 Permanet Generation,大小可以通過參數來設置,可以通過-XX:PermSize指定初始值,-XX:MaxPermSize指定最大值。

5. Constant Pool常量池:

方法區有一個非常重要的區,叫做運行時常量池(RCP)。常量池中存儲了如字符串、final變量值、類名和方法名常量。常量池在編譯期間就被確定,在字節碼文件(Class文件)中,除了有類的版本、字段、方法、接口等先關信息描述外,還有常量池(Constant Pool Table)信息。用於存儲編譯器產生的字面量和符號引用。這部分內容在類被加載後,都會存儲到方法區中的RCP。字面量就是字符串、final變量等。類名和方法名屬於引用量。引用量最常見的是在調用方法的時候,根據方法名找到方法的引用,並以此定為到函數體進行函數代碼的執行。引用量包含:類和接口的權限定名、字段的名稱和描述符,方法的名稱和描述符。值得註意的是,運行時產生的新常量也可以被放入常量池中,比如 String 類中的 intern() 方法產生的常量。

6.Native Method Stack 本地方法棧:

本地方法棧和Java棧所發揮的作用非常相似,區別不過是Java棧為JVM執行Java方法服務,而本地方法棧為JVM執行Native方法服務。本地方法棧也會拋出StackOverflowError和OutOfMemoryError異常。

這裏重點要說的是堆裏對象實例的分配和存儲:

  java是面向對象的語言,因此對象的創建無時無刻都存在。在語言層面,使用new關鍵字即可創建出一個對象。但是在虛擬機中,對象創建的創建過程則是比較復雜的。

  首先,虛擬機運到new指令時,會去常量池檢查是否存在new指令中包含的參數,比如new People(),則虛擬機首先會去常量池中檢查是否有People這個類的符號引用,並且檢查這個類是否已經被加載了,如果沒有則會執行類加載過程。

  在類加載檢查過後,接下來為對象分配內存當然是在java堆中分配,並且對象所需要分配的多大內存在類加載過程中就已經確定了。為對象分配內存的方式根據java堆是否規整分為兩個方法:1、指針碰撞(Bump the Pointer),2、空閑列表(Free List)。指針碰撞:如果java堆是規整的,即所有用過的內存放在一邊,沒有用過的內存放在另外一邊,並且有一個指針指向分界點,在需要為新生對象分配內存的時候,只需要移動指針畫出一塊內存分配和新生對象即可;空閑列表:當java堆不是規整的,意思就是使用的內存和空閑內存交錯在一起,這時候需要一張列表來記錄哪些內存可使用,在需要為新生對象分配內存的時候,在這個列表中尋找一塊大小合適的內存分配給它即可。而java堆是否規整和垃圾收集器是否帶有壓縮整理功能有關。

 1. 在為新生對象分配內存的時候,同時還需要考慮線程安全問題。因為在並發的情況下內存分配並不是線程安全的。有兩種方案解決這個線程安全問題,1、為分配內存空間的動作進行同步處理;2、為每個線程預先分配一小塊內存,稱為本地線程分配緩存(Thread Local Allocation Buffer, TLAB),哪個線程需要分配內存,就在哪個線程的TLAB上分配。內存分配後,虛擬機需要將每個對象分配到的內存初始化為0值(不包括對象頭),這也就是為什麽實例字段可以不用初始化,直接為0的原因。接來下,虛擬機對對象進行必要的設置,例如這個對象屬於哪個類的實例,如何找到類的元數據信息。對象的哈希嗎、對象的GC年代等信息,這些信息都存放在對象頭之中。執行完上面工作之後,所有的字段都為0,接著執行<init>指令,把對象按照程序員的指令進行初始化,這樣一個對象就完整的創建出來。

2、對象的內存布局

  對象在內存的存儲布局中包括:對象頭、實例數據、對齊填充

  對象頭(Header):包含兩部分信息。1、存儲對象自身的運行時數據,比如哈希碼、GC分代年齡等;2、類型指針:通過這個指針確定這個對象屬於哪個類。

  實例數據(Instance Data):存儲代碼中定義的各種類型的字段內容。

  對齊填充(Padding):這部分信息沒有任何意義,僅僅是為了使得對象占的內存大小為8字節的整數倍。

3、對象的訪問定位

  創建對象是為了使用對象,java程序需要通過棧上的reference數據來操作棧上的具體對象。目前主流的訪問對象方式有使用句柄和直接指針兩種。1、使用句柄方式:會在java堆中創建一個句柄池,reference指向的這塊句柄池,句柄池中包括兩個指針,其中一個指針指向對象實例數據,另外一個指針指向對象的類型數據。2、使用指針的方式:reference存儲的直接就是對象的地址。

  兩種方式各有各的特點,如果使用句柄方式的話,最大的好處是reference存放的是穩定的句柄地址,在對象移動時只會改變句柄中的實例數據指針,而reference本身不需要修改。使用指針的方式優勢則是速度快,並且省去了一次指針定位的開銷。

更詳細的可以參考:

https://www.cnblogs.com/lewis0077/p/5143268.html

https://www.cnblogs.com/lingepeiyong/archive/2012/10/30/2745973.html

JVM學習-之對象的創建和內存分配