一 JVM之記憶體分割槽

2022-09-22 22:48:25 字數 4238 閱讀 9560

jvm將執行j**a程式的記憶體劃分為不同的資料區域

1.1 程式計數器

​ 程式計數器是記憶體區域中一塊比較小的記憶體空間。它的作用就像是乙個指向正在執行的位元組碼行號的指標。改變計數器的值來指向當前要執行的位元組碼指令。

​ 並且由於j**a多執行緒也是分配時間片的模式來實現的,在單核處理器過程中,執行緒的切換後為了指向正確的執行位置,也需要程式計數器。

​ 同時,為了各執行緒的執行進度互不影響,所以將其獨立儲存,是執行緒的私有空間。

若當前執行的是native方法,計數器則為空。

1.1.1 error

程式計數器是唯一乙個在j**a虛擬機器規範中沒有規定任何outofmemoryerror異常的區域。

1.2 虛擬機器棧

​ 由於執行緒私有,因此其生命週期與執行緒相同,同生共死。

虛擬機器棧是j**a方法執行的記憶體模型。每個方法在執行時,都會建立乙個棧幀。該棧幀會儲存一些資訊(區域性變數表、運算元棧、動態鏈結、方法出口等),然後被壓入棧中,待該方法執行完後再彈出。

注:區域性變數表所需的內存在編譯期間就已經完成分配,進入方法是,棧幀需要分配多大的區域性變數表是完全確定的,在執行期間不會改變)

注:

編譯期間完成分配:是指,在編譯時期,在程式位元組碼內生成一些指令,由這些指令控制程式在執行時分配好記憶體。

執行期分配:是指,執行時才確定分配的大小,儲存位置也是在執行時才知道。

1.2.1 error

​ 該區域存在兩種異常

​ outofmemoryerror由棧的動態深度造成。當棧無法申請到足夠的記憶體時丟擲。

1.3 本地方法棧

​ 生命週期與執行緒相同。

​ 其作用與虛擬機器棧非常相似,區別在於虛擬機器棧為j**a方法服務,而本地方法棧,顧名思義,是為虛擬機器使用到的native方法服務

​ 虛擬機器規範中對本地方法棧的實現方式沒有強制規定,如何實現全看虛擬機器的設計者,有些虛擬機器甚至會將虛擬機器棧和本地方法棧合二為一。

1.2.1 error

​ 與虛擬機器棧一樣。

2.1 j**a堆

​ jvm所管理的記憶體中最大的一塊。被所有執行緒共享。在虛擬機器啟動時建立(生命週期區別於上述三個)。

j**a堆的唯一目的就是存放物件例項(幾乎所有的物件例項和陣列都要在j**a堆上分配)。

​ j**a堆是垃圾收集管理的主要區域,有時也被稱為「gc堆」。並且由於所有收集器都採用分代演算法,j**a堆中還可細分為:新生代和老年代。再細還可分為 eden、from survivor、to survivor空間等。儘管j**a堆是共享的,但還是有可能劃分出多個執行緒私有的分配緩衝區。

j**a堆不要求物理空間連續,只要邏輯連續即可。

2.1.1 error

​ 若堆中再無空間可以完成例項分配,將會丟擲oom異常。

2.2 方法區

​ 執行緒共享。

​ 用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即使編譯器編譯後的**等資料。方法區儲存的資料和方法的執行沒有關係,而和類載入有關。

​ 方法區可以選擇不實現垃圾收集。但相對而言,實現了垃圾收集的虛擬機器在這個區域的垃圾收集行為也是比較少的,方法區記憶體**主要是針對常量池的**和對型別的解除安裝。

2.2.1 error

​ 當方法區無法滿足記憶體分配需求時,丟擲oom異常。

2.2.2 執行時常量池

​ 執行時常量池時方法區的一部分。

​ 常量池,用於儲存編譯期生成的各種字面量和符號引用,這部分內容在類載入後進入方法區的執行時常量池中存放。

(類載入後,執行時常量池會儲存class檔案常量池;類解析後,執行時常量池會儲存符號引用對應的直接引用)

(當然常量並非只有編譯器能夠產生,執行期間也能將新的常量放入常量池中)

作用:常量池為了避免頻繁的建立和銷毀物件而影響系統效能,實現了物件的共享。

​ 例如字串常量池,在編譯階段九八所有的字串文字放到乙個常量池中。

​ (1)節省記憶體空間。常量池中所有相同的字串常量被合併,只占用乙個空間。

error

​ 常量池無法再申請到記憶體時會丟擲outofmemory異常。

​ 並非虛擬機器記憶體的部分,也不是虛擬機器規範中定義的記憶體區域。

​ jdk1.4中新加入的nio類和通道與緩衝區的i/o方式,使得可以使用native函式庫直接分配堆外記憶體,然後通過儲存在j**a堆內的directbytebuffer物件作為這塊記憶體的引用進行操作。

​ 該部分雖然不受制於jvm記憶體的限制,但依然會受到本機的記憶體(ram、swap區)大小限制。因此當各個區域記憶體總和大於物理記憶體限制時將會丟擲oom異常。

上面所提到的代表乙個類的符號引用,在位元組碼載入階段,就會被載入到方法區中的常量池中。

物件所需大小在類載入時就已經確定大小。這裡主要介紹兩種jvm記憶體分配的方式。

1.指標碰撞bump the pointer

這種情況是假設堆中記憶體絕對規整,即一邊為用過,一邊為空閒記憶體,每次分配空間只需要將指向邊界的記憶體右移即可。

2.空閒列表 free list

若j**a堆中記憶體不夠規整,就不能進行簡單的指標碰撞,而是用維護乙個列表的方式。

列表中記錄了那些記憶體塊是可用的,在分配時從列表中找到一塊足夠大的空間分給物件例項。

上述兩種分配方式由j**a堆記憶體分配是否規整決定,而j**a堆記憶體的是否規整又由垃圾收集器採用的垃圾收集演算法是否帶有壓縮整理功能決定。因此不同的收集器會採用不同的分配方法。

造成執行緒安全問題的原因是,j**a物件的建立及記憶體分配在jvm中是一件非常頻繁的事情,我們很可能會遇多個執行緒搶占時間片後都為物件分配記憶體的現象,而記憶體分配的行為本質上來說就是修改指標的值(指標指向的位址)。

很有可能出現,a物件已經分配好記憶體,準備做出修改,此時發生了中斷,而切換到其他執行緒為b物件分配記憶體。等待a物件的執行緒再來完成剛剛的分配修改動作時,記憶體已經被占用,造成了執行緒不安全。

解決方案1:基於cas

基於cas對分配記憶體空間的動作保證原子性,再配上失敗重試。

解決方案2:本地執行緒分配緩衝 tlab (thread local allocation buffer)

預先在j**a堆中為每個執行緒分配一小塊記憶體,稱為tlab,只有在tlab用完並重新分配tlab時,才需要同步鎖定。

記憶體分配完畢後,jvm將分配的空間都初始化為對應資料型別的零值,若使用tlab,這步可以提前至tlab分配前進行。(保證了j**a**不賦初始值就可以執行)

在hotspot虛擬機器中,物件在記憶體中的布局可以分為3塊區域:物件頭 、 例項資料 和 對齊填充。

物件頭 header

儲存兩部分資訊

第一部分儲存物件自身執行時的資料 mark word

​ 該部分包括,雜湊碼,gc分代年齡,鎖狀態標誌,執行緒持有鎖、偏向執行緒id、偏向時間戳等。

第二部分儲存型別指標(物件指向類元資料的指標)

​ jvm通過該指標來確定這個物件是哪個類建立的。

若物件是乙個j**a陣列,則物件頭中還必須包含陣列長度的資料。

例項資料instance data

該部分是物件真正儲存的有效資訊,是程式**中所定義的各種型別的字段內容。

無論從父類繼承下來,還是在子類中定義的,都需要記錄起來。

C 之記憶體分割槽

程式執行後 new操作符 c 程式在執行時,將記憶體大方向劃分為4個區域 不同區域存放的資料,賦予不同的生命週期 在程式編譯後,生成了exe可執行程式,未執行該程式前分為兩個區域 區 存放 cpu 執行的機器指令 區是共享的,共享的目的是對於頻繁被執行的程式,只需要在記憶體中有乙份 即可 區是唯讀的...

c 學習之記憶體分割槽模型

c 程式在執行時,將記憶體大致分為4個取區域。區 存放函式體的二進位制 由作業系統進行管理。全域性區 存放全域性變數和靜態變數以及常量 棧區 由編譯器自動分配釋放,存放函式的引數值 區域性變數等 堆區 由程式設計師分配和釋放,若程式設計師不釋放,則程式結束時由作業系統 在程式編譯後,生成了.exe可...

C 學習之記憶體分割槽模型

c 程式執行時,主要分為4個區域 區 存放函式體的二進位制 由作業系統進行管理 全域性區 存放全域性變數和靜態變數以及常量 棧區 由編譯器自動分配釋放,存放函式的引數值 區域性變數等 堆區 由程式設計師分配釋放,若程式設計師不釋放,程式結束時作業系統 有四個區的概念對於程式設計靈活性提高有很大的幫助...