1、物件的建立
A a = new A()
A:引用的型別
a::引用的名稱
new A():建立一個A類物件
當建立一個物件時,具體建立過程是什麼呢?
(1)JVM遇到new的位元組碼指令後,檢查類是否被載入,否,進行類載入
(2)檢查載入通過後,對新建立的物件在堆中分配記憶體
(3)將分配的記憶體空間進行初始化為0值
(4)設定物件頭的資訊,將物件的所屬類(即類的後設資料資訊)、物件的HashCode、物件的GC資訊、鎖資訊等資料儲存在物件頭中
(5)呼叫物件的構造方法進行初始化
2、物件記憶體的分配策略
物件建立的過程中需要為新物件在堆上劃分出一塊確定大小的記憶體空間,JVM中對於劃分記憶體有兩種策略,指標碰撞和空閒列表
指標碰撞:當記憶體空間絕對規整,使用中的記憶體放一邊,未使用的記憶體放另一邊,中間放由一個作為分界器的指標,當進行記憶體分配時,指標向空閒記憶體方向挪動與物件大小相等的距離
空閒列表:當堆上的記憶體空間不是絕對規整,使用和未使用的記憶體空間呈犬牙交錯的形勢,此時虛擬機器需要維護一個列表,列表中記錄了那塊記憶體未被使用,分配記憶體時需要在列表中找到一塊足夠大的記憶體空間或分給新建的物件,並更新表中的記錄。
其中指標碰撞的分配策略效能要更高一些,JVM採用哪種分配策略是由堆上記憶體空間是否絕對規整來決定的,記憶體空間是否絕對規整是由JVM採用哪種GC來決定的
給物件劃分記憶體空間時,不僅要考慮記憶體的分配策略,還需要考慮到記憶體分配時的併發安全,JVM中是怎樣確保記憶體分配時的併發安全呢?
JVM中建立物件十分的頻繁,當物件A建立時,剛為其分配記憶體,還未更新指標或者列表時,物件B來建立,此時就會發生問題
JVM中為了保證併發情況下執行緒安全,採用了兩種方案:CAS失敗重試和分配緩衝
CAS失敗重試:CAS(Compare-and-Swap),即比較並替換,是一種實現併發演算法時常用到的技術,Java併發包(Java.Util.Concurrent)中的原子類都使用了CAS技術。
CAS需要有3個運算元:記憶體地址V,舊的預期值A,即將要更新的目標值B。
CAS指令執行時,當且僅當記憶體地址V的值與預期值A相等時,將記憶體地址V的值修改為B,否則就什麼都不做。整個比較並替換的操作是一個原子操作。
CAS失敗重試流程:一塊空白的記憶體,此時是null值,在空白的記憶體中劃分出一塊與申請物件大小一致的記憶體,劃分完之後,再來看記憶體是否為null,是,為物件分配記憶體成功,否,說明在劃分的過程中有別的執行緒來對這塊記憶體進行了分配的操作,為物件分配記憶體失敗,找到下一塊空白的記憶體,繼續上述操作
分配緩衝:把記憶體分配的動作按照執行緒劃分在不同的空間之中進行,即每個執行緒在 Java 堆中預先分配一小塊私有記憶體,也就是本地執行緒分配緩衝(Thread Local Allocation Buffer,TLAB),JVM 線上程初始化時,同時也會申請一塊指定大小的記憶體,只給當前執行緒使用,這樣每個執行緒都單獨擁有一個 Buffer,如果需要分配記憶體,就在自己的 Buffer 上分配,這樣就不存在競爭的情況,可以大大提升分配效率,當 Buffer 容量不夠的時候,再重新從 Eden 區域申請一塊繼續使用。
物件大小為8的整數倍,方便記憶體的劃分
定位物件的方式有兩種:控制程式碼和直接指標
控制程式碼:JVM在堆上劃分出一塊記憶體作為控制程式碼池,引用(reference)中儲存的物件就是控制程式碼的地址,控制程式碼中包含了物件的例項資料與型別資料真實的地址資訊
優點:引用 中儲存的是穩定的控制程式碼地址,在物件被移動(垃圾收集時移動物件是非常普遍的行為)時只會改變控制程式碼中的例項資料指標,而引用本身不需要修改
直接指標:引用(reference)中儲存的物件就是真實地址,Sun HotSpot 是使用直接指標訪問方式進行物件訪問的
優點:較比控制程式碼速度要快一些,因為它節省了一次指標定位的時間開銷
6、引用的型別
強引用:一般的 Object obj = new Object() ,就屬於強引用。在任何情況下,只有有強引用關聯(與根可達)還在,垃圾回收器就永遠不會回收掉被引用的物件
軟引用:一些有用但是並非必需,用軟引用關聯的物件,系統將要發生記憶體溢位(OuyOfMemory)之前,這些物件就會被回收(如果這次回收後還是沒有足夠的
弱引用:一些有用(程度比軟引用更低)但是並非必需,用弱引用關聯的物件,只能生存到下一次垃圾回收之前,GC 發生時,不管記憶體夠不夠,都會被回收
虛引用:幽靈引用,最弱(隨時會被回收掉)
7、如何判斷物件是否存活
判斷物件是否存活的方式有兩種:引用計數法和可達性分析
引用計數法:在物件中新增一個引用計數器,每當有一個地方引用它,計數器就加 1,當引用失效時,計數器減 1
這種方法Python中使用,JVM中沒有使用
可達性分析:通過以GC Roots物件為起點,向下搜尋,看是否存在引用,搜尋走過的路被稱為引用鏈,當一個物件到GC Roots沒有任何引用鏈,說明此物件是無用可被回收的
GC Roots物件:虛擬機器棧(棧幀中的本地變數表)中引用的物件
各個執行緒呼叫方法堆疊中使用到的引數、區域性變數、臨時變數等
Finalize方法:即使通過可達性分析判斷不可達的物件,也不是“非死不可”,它還會處於“緩刑”階段,真正要宣告一個物件死亡,需要經過兩次標記過程,一次是沒有找到與 GCRoots 的引用鏈,它將被第一次標記。隨後進行一次篩選(如果物件覆蓋了 finalize),我們可以在 finalize 中去拯救