對象創建:當虛擬機遇到一條new指令時,首先將檢查這個指令參數能否在常量池中定位到一個類的符號引用,並檢查這個類的符號引用是否被載入、解析和初始化,如果沒有,必須先執行類的載入過程。在類完成載入後,虛擬機便會為類分配記憶體,而記憶體的大小在類載入完成時就已經確定了,若Java堆的記憶體是絕對規整的,即用過...
對象創建:當虛擬機遇到一條new指令時,首先將檢查這個指令參數能否在常量池中定位到一個類的符號引用,並檢查這個類的符號引用是否被載入、解析和初始化,如果沒有,必須先執行類的載入過程。
在類完成載入後,虛擬機便會為類分配記憶體,而記憶體的大小在類載入完成時就已經確定了,若Java堆的記憶體是絕對規整的,即用過的記憶體放一邊,空閑的記憶體放另一邊,中間用指針作為分界點隔開,那麼記憶體分配時僅僅是把中間指針向空閑空間那邊移動一段與對象大小相等的距離即可,這種記憶體分配方式稱為“指針碰撞”(Bump the pointer)。若Java堆的記憶體並不規整,用過的記憶體和空閑的記憶體相互交錯,那麼虛擬機必須維護一個列表來記錄哪些記憶體區是可用的,在分配時找一塊足夠大的區域給對象實例,並更新列表,這種分配方式稱為“空閑列表”(Free List)。Java堆是否規整是由垃圾回收器是否帶有壓縮整理功能決定的。在使用Serial、ParNew等帶Compact過程的垃圾收集器時,系統採用的分配演算法是指針碰撞,而使用CMS這種基於Mark-Sweep的收集器時,採用空閑列表的分配演算法。
對象的創建在虛擬機中十分的頻繁,即使是僅僅改變一個指針所指向的位置,在併發的情況下也不是線程安全的。可能出現給對象A分配記憶體,還沒修改指針的情況下,對象B使用了原來的指針來分配記憶體的情況。解決這個問題有兩種方案:一種是對記憶體分配空間的操作進行同步處理,另一種是把記憶體分配的動作按線程劃分在不同的空間中進行,即每個線程在Java堆中預先分配一小塊記憶體(Thread Local Allication Buffer,TLAB)。哪個線程要分配記憶體,就在哪個線程的TLAB上分配,只有TLAB用完並分配新的TLAB時,才需要同步鎖定。記憶體分配完後,虛擬機會對記憶體空間初始化為零值,併在對象頭(Object Header)中設置類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息,執行完new指令後,會接著執行<init>方法,這樣,一個真正可用的對象才算完全產生出來。
對象的記憶體佈局:對象在記憶體中的存儲佈局可以分為對象頭(Object Header)、實例數據(Instant Data)、和對齊填充(Padding)。
對象頭又包括兩部分信息,第一部分包括自身的運行時數據,如哈希碼、GC分代年齡、鎖狀態標識、自身持有的鎖、偏向線程ID、偏向時間戳等,另一部分是類型指針,即對象指向它的類元數據的指針,虛擬機通過這個指針來確定對象是哪個類的實例。如果對象是Java數組,那麼對象頭中還應記錄數組的長度,因為虛擬機可以通過普通Java對象的元數據信息確定對象大小,但是從數組的元數據中無法確定數組的大小。
實例數據部分是對象真正存儲的有效信息,也是程式中定義的各種類型的欄位內容。存儲的順序受到虛擬機分配策略參數和欄位在Java源碼中的定義順序的影響。
對齊填充僅僅起占位符的作用,不是必然存在的。HotSpot中要求對象的起始地址必須是8位元組的整數倍。
對象的訪問定位:對象訪問方式分 使用句柄 和 直接指針 兩種
使用句柄: