JVM 記憶體物件管理

2022-09-16 00:09:16 字數 3653 閱讀 9698

先來看一下 jvm 管理的記憶體圖:

程式計數器

它是一塊較小的記憶體空間,它的作用可以看做是當先執行緒所執行的位元組碼的訊號指示器。

每一條jvm執行緒都有自己的pc暫存器,各條執行緒之間互不影響,獨立儲存,這類記憶體區域被稱為「執行緒私有」記憶體

此記憶體區域是唯一乙個在j**a虛擬機器規範中沒有規定任何outofmemoryerror情況的區域。

方法區是被各個執行緒共享的記憶體區域,用於儲存以被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的**等資料。雖然這個區域被虛擬機器規範把方法區描述為堆的乙個邏輯部分,但是它的別名叫非堆,用來與堆做一下區別。

方法區在虛擬機器啟動的時候建立。方法區在實際記憶體空間中可以是不連續的。

當方法區無法滿足記憶體分配需求時就會拋outofmemoryerror。

執行時常量池(runtime constant pool)

它是方法區的一部分。class檔案中除了有類的版本、字段、方法、介面等描述等資訊外,還有一項資訊是常量池(constant pool table),用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類載入後存放到方法區的執行時常量池中。j**a虛擬機器對class檔案的每一部分(自然也包括常量池)的格式都有嚴格的規定,每乙個位元組用於儲存哪種資料都必須符合規範上的要求,這樣才 會被虛擬機器認可、裝載和執行。但對於執行時常量池,j**a虛擬機器規範沒有做任何細節的要求,不同的提供商實現的虛擬機器可以按照自己的需要來實現這個記憶體 區域。不過,一般來說,除了儲存class檔案中描述的符號引用外,還會把翻譯出來的直接引用也儲存在執行時常量池中。執行時常量池相對於class檔案常量池的另外乙個重要特徵是具備動態性,j**a語言並不要求常量一定只能在編譯期產生,也就是並非預置入 class檔案中常量池的內容才能進入方法區執行時常量池,執行期間也可能將新的常量放入池中,這種特性被開發人員利用得比較多的便是string類的 intern()方法。既然執行時常量池是方法區的一部分,自然會受到方法區記憶體的限制,當常量池無法再申請到記憶體時會丟擲outofmemoryerror異常。

虛擬機器棧

虛擬機器棧也是執行緒私有的。每乙個jvm執行緒都有自己的j**a虛擬機器棧,這個棧與執行緒同時建立,它的生命週期與執行緒相同。

虛擬機器棧描述的是j**a方法執行的記憶體模型:每個方法被執行的時候都會同時建立乙個棧幀(stack frame)用於儲存區域性變數表、運算元棧、動態鏈結、方法出口等資訊。每乙個方法被呼叫直至執行完成的過程就對應著乙個棧幀在虛擬機器棧中從入棧到出棧的過程。

如果執行緒請求的棧深度大於虛擬機器所允許的深度將丟擲stackoverflowerror;

本地方法棧

和虛擬機器棧類似,只是它只為虛擬機器用到的native方法服務

堆虛擬機器管理的記憶體中最大的一塊,同時也是被所有執行緒所共享的,它在虛擬機器啟動時建立,這貨存在的意義就是存放物件例項,幾乎所有的物件例項以及陣列都要在這裡分配記憶體。這裡面的物件被自動管理,也就是俗稱的gc(garbage collector)所管理。用就是了,有gc扛著呢,不用操心銷毀**的事兒。

j**a堆的容量可以是固定大小,也可以隨著需求動態擴充套件(-xms和-xmx),並在不需要過多空間時自動收縮。

j**a堆所使用的記憶體不需要保證是物理連續的,只要邏輯上是連續的即可。

jvm實現應當提供給程式設計師調節j**a 堆初始容量的手段,對於可動態擴充套件和收縮的堆來說,則應當提供調節其最大和最小容量的手段。

如果堆中沒有記憶體完成例項分配並且堆也無法擴充套件,就會拋outofmemoryerror。

物件管理

虛擬機器遇到一條new指令時,首先將去檢查這個指令的引數是否能在方法區中定位到乙個類的符號引用,並且檢查這個符號引用代表的類是否已被載入、解析和初始化。如果沒有,那必須先執行相應的類載入過程。

在類載入檢查通過後,接下來虛擬機器將為新生物件分配記憶體。物件所需記憶體的大小在類載入完成後便可完全確定,為物件分配空間的任務等同於把一塊確定大小的記憶體從j**a堆中劃分出來。假設j**a堆中記憶體是絕對規整的,所有用過的記憶體都放在一邊,空閒的記憶體放在另外一邊,中間放著乙個指標作為分界點的指示器,那所分配記憶體就僅僅是把那個指標向空閒空間那邊挪動一段與物件大小相等的距離,這種分配方式成為:「指標碰撞」(bump the pointer)。

如果j**a對中的記憶體並不是規整的,已使用的記憶體和空閒的記憶體相互交錯,那就沒有辦法簡單地進行指標碰撞了,虛擬機器就必須維護乙個列表,記錄上哪些記憶體記憶體塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給物件例項,並更新列表上的記錄,這種分配方式成為:「空閒列表」(free list)。選擇哪種分配方式由j**a堆是否規整決定,而j**a對是否規整又由所採用的垃圾收集器是否帶有壓縮整理功能決定。因此,在使用serial、parnew等待compact過程的收集器時,系統採用的分配演算法時指標碰撞,而是用cms這種基於mark-sweep演算法的收集器時,通常採用空閒列表。

物件在記憶體中的布局可以分為三個區域:物件頭(header),例項資料(instance data),對齊填充(padding)

物件頭(header)

hotspot虛擬機器的物件頭包括兩部分的資訊

第一部分儲存物件自身的執行時資料,如雜湊碼,gc分代年齡,鎖狀態標識,執行緒持有的鎖,偏向執行緒id,偏向時間戳。這部分資料的長度在32位和64位虛擬機器中的長度為32bit和64bit

另一部分是型別指標,即物件指向他的類元資料的指標,虛擬機器通過這個指標確定這個物件是哪個類的例項。如果j**a物件是乙個陣列,那在物件頭中還必須有一塊用於記錄陣列長度的資料。

例項資料(instance data)

真正儲存的有效資訊。也是程式**中定義的各種型別的字段內容。無

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

這部分的儲存資訊會受到虛擬機器分配策略引數(fieldsallocationstyle)和字段在j**a原始碼中定義的順序的影響。

對齊填充(padding)

對齊填充不是必然存在的,沒有特別的含義,僅僅起著佔位符的作用。hotspot要求物件其實位址必須是8的整數倍。物件頭部分正好是8位元組的倍數,而例項資料部分則不一定,所以這部分需要對齊填充。

控制代碼訪問方式:j**a堆中將劃分出一塊記憶體來作為控制代碼池,reference中儲存的就是物件的控制代碼位址,而控制代碼中包含了物件例項資料和型別資料各自的具體位址資訊。

指標訪問方式:reference變數中直接儲存的就是物件的位址,而j**a堆物件一部分儲存了物件例項資料,另外一部分儲存了物件型別資料。

總結:

這兩種物件訪問方式各有優勢,使用控制代碼來訪問的最大好處

就是reference中儲存的是穩定的控制代碼位址,在物件被移動

(垃圾收集時移動物件是非常普遍的)

時只會改變控制代碼中的例項資料指標,而reference本身不需要修改;而缺點是訪問物件時需從控制代碼池中獲取具體位置後再定位具體的內容,由於物件的訪問在j**a中非常頻繁,故這類開銷積少成多也是一項非常可觀的執行成本。

使用直接指標訪問方式的最大好處就是速度更快,它節省了一次指標定位的時間開銷。我們常用的sun hotspot虛擬機器而言,它是使用第二種方式進行物件的訪問的。

OC中記憶體管理 MRC 多物件管理

2019獨角獸企業重金招聘python工程師標準 將以乙個精武團這個遊戲來做例子。遊戲者需要開房,那麼這個房子就這個人使用,當這個人還在這個房間的時候就不能釋放這個房間,否則這個遊戲就有個bug,同樣的道理,這個房子可以被多個人引用。那什麼時候釋放這個房子就是個關鍵 當這個房子無人使用的時候就是釋放...

測試物件管理

1 uft中物件的概念 uft中的物件有兩個概念,乙個是測試物件,乙個是執行時物件 1 測試物件 test object,to 是uft定義的一些類,用它們代表被測應用的各種物件。2 執行時物件 runtime object,ro 是實際的被測應用物件,是測試執行過程中,to用來關聯的物件。2 uf...

物件管理資源

今天看了下effective c 的條款13 以物件管理資源,感覺十分有理,特此做一下筆記。假設我們使用乙個用來描述投資行為的程式庫,其中各式各樣的投資型別都繼承自乙個根類 investment 投資型別繼承體系中的root class class investment 這裡呢,我們進一步假設這個程...