目錄
一、對象內存布局
1、對象的實例化
1.1、你有幾種方式創建對象?
1.2、創建對象的步驟
1.2.1、從字節碼角度看待對象創建過程
1.2.2、從執行步驟角度分析
2、對象的內存布局
2.1、對象頭
2.2、實例數據
2.3、對齊填充
3、對象的訪問定位
3.1、句柄訪問
3.2、直接使用指針訪問
HotSpot使用哪種方式呢?
一、對象內存布局
1、對象的實例化
1.1、你有幾種方式創建對象?
? 1、new ①最常見的方式 ②變形1:Xxx的靜態方法 ③變形2:XxxBuilder/XxxFactory的靜態方法
? 2、Class的newInstance():反射的方式,可以調用空參、帶參的構造器,權限必須是public
? 3、Constructor的newInstance(Xxx):反射的方式,可以調用空參、帶參的構造器,權限沒有要求,實用性更廣
? 4、使用clone():不調用任何構造器,當前類需要實現Cloneable接口,實現clone(),默認淺拷貝
? 5、使用反序列化:從文件中,數據庫中,網絡中獲取一個對象的二進制流,反序列化為內存中的對象
? 6、第三方庫Objenesis,利用了asm字節碼技術,動態生成Constructor對象
1.2、創建對象的步驟
1.2.1、從字節碼角度看待對象創建過程
(1)、下面從最簡單的0bject ref=new object(); 代碼進行分析,利用javap-verbose -p 命令查看對象創建的字節碼如下:
cmd命令執行class
在IDE編譯器上面會被轉換成如下
NEW :如果找不到class對象,則進行類加載。加載成功后,則在堆中分配內存,從0bject開始到本類路徑上的所有屬性值都要分配內存。分配完畢之后,進行零值初始化。在分配過程中,注意引用是占據存儲空間的,它是一個變量,占用4個字節。這個指令完畢后,將指向實例對象的引用變量壓入虛擬機棧頂。
DUP :在棧頂復制該引用變量,這時的棧頂有兩個指向堆內實例對象的引用變量。如果<init>方法有參數,還需要把參數壓人操作棧中。兩個引用變量的目的不同,其中壓至底下的引用用于賦值,或者保存到局部變量表,另一個棧頂的引用變量作為句柄調用相關方法。
INVOKESPECIAL:調用對象實例方法,通過棧頂的dup引用變量調用<init>方法。
補充:<clinit>是類初始化時執行的方法,而<init>是對象初始化時執行的方法。
面試題:
1、new對象流程?
2、對象創建方法,對象內存分配?
1.2.2、從執行步驟角度分析
1、判斷對象對應的類是否加載、鏈接、初始化
? ? 虛擬機遇到一條new指令,首先去檢查這個指令的參數能否在Metaspace的常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否已經被加載、解析和初始化。(即判斷類元信息是否存在)。
- 如果沒有,那么在雙親委派模式下,使用當前類加載器以classLoader+包名+類名為Key進行查找對應的.class 文件。
- 如果沒有找到文件,則拋出ClassNotFoundException 異常。。
- 如果找到,則進行類加載,并生成對應的Class類對象。
2、為對象分配內存
? ? 首先計算對象占用空間大小,接著在堆中劃分一塊內存給新對象。如果實例成員變量是引用變量,僅分配引用變量空間即可,即4個字節大小。
? ? 說明:選擇哪種分配方式由Java堆是否規整決定,而Java堆是否規整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。
? ? ①指針碰撞
? ? ? ? 如果內存規整,使用指針碰撞
? ? 如果內存是規整的,那么虛擬機將采用的是指針碰撞法(BumpThe Pointer)來為對象分配內存。意思是所有用過的內存在一邊,空閑的內存在另外一邊,中間放著一個指針作為分界點的指示器,分配內存就僅僅是把指針向空閑那邊挪動一段與對象大小相等的距離罷了。 如果垃圾收集器選擇的是Serial、ParNew這種基于壓縮算法的,虛擬機采用這種分配方式。 一般使用帶有compact(整理)過程的收集器時,使用指針碰撞。
? ? ②空閑列表
? ? ? ? 如果內存不規整,虛擬機需要維護一個列表,使用空閑列表分配
? ? 如果內存不是規整的,已使用的內存和未使用的內存相互交錯,那么虛擬機將采用的是空閑列表法來為對象分配內存。意思是虛擬機維護了一個列表,記錄上哪些內存塊是可用的再分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,并更新列表上的內容。這種分配方式稱為“空閑列表(Free List)”。
3、處理并發安全問題
? ? 在分配內存空間時,另外一個問題是及時保證new對象時候的線程安全性:創建對象是非常頻繁的操作,虛擬機需要解決并發問題。 虛擬機采用了兩種方式解決并發問題:
- CAS(Compare And Swap )失敗重試、區域加鎖:保證指針更新操作的原子性;
- TLAB 把內存分配的動作按照線程劃分在不同的空間之中進行,即每個線程在Java堆中預先分配一小塊內存,稱為本地線程分配緩沖區,(TLAB,Thread Local Allocation Buffer)虛擬機是否使用TLAB,可以通過-XX:+/-UseTLAB參數來設定
4、初始化分配到的空間
? ? 內存分配結束,虛擬機將分配到的內存空間都初始化為零值(不包括對象頭)。這一步保證了對象的實例字段在Java代碼中可以不用賦初始值就可以直接使用,程序能訪問到這些字段的數據類型所對應的零值。
5、設置對象的對象頭
? ? 將對象的所屬類(即類的元數據信息)、對象的Hashcode和對象的GC信息、鎖信息等數據存儲在對象的對象頭中。這個過程的具體設置方式取決于JVM實現。
6、執行init方法進行初始化
? ? 在Java程序的視角看來,初始化才正式開始。初始化成員變量,執行實例化代碼塊,調用類的構造方法,并把堆內對象的首地址賦值給引用變量。
? ? 因此一般來說(由字節碼中是否跟隨有invokespecial指令所決定),new指令之后會接著就是執行方法,把對象按照程序員的意愿進行初始化,這樣一個真正可用的對象才算完全創建出來。
2、對象的內存布局
2.1、對象頭
2.2、實例數據
2.3、對齊填充
3、對象的訪問定位
3.1、句柄訪問
3.2、直接使用指針訪問
HotSpot使用哪種方式呢?
JVM學習(一)
JVM學習(三)--運行時數據區
再小的努力,乘以365都很明顯!
每天??記錄?點點。內容也許不重要,但習慣很重要!
一個程序員最重要的能力是:寫出高質量的代碼!!
有道無術,術尚可求也,有術無道,止于術。
無論你是年輕還是年長,所有程序員都需要記住:時刻努力學習新技術,否則就會被時代拋棄!