1. 程式人生 > 實用技巧 >Java 新建物件及物件儲存

Java 新建物件及物件儲存

關於JAVA new Object() 新建物件的幾個問題

問題1:物件的建立過程

在這裡插入圖片描述
第一行彙編碼,申請空間,並初始化成員變數為預設值(半初始化)
第二行:dup 複製 (因為第三行的invokespecial會消耗一個引用,所以必須先複製一個)
第三行:呼叫T的 構造方法,初始化m為8
第四行:astore-1 把t和new的物件連線起來

第二題: 單例模式:應用執行期間記憶體中只能有一個物件

第一種寫法:一來先new一個private static 的物件,將構造方法設為 private ,設定一個public函式getInstance來獲取這個物件
在這裡插入圖片描述
第二種寫法:
要在需要的時候才new這個物件,先不new,等到需要的時候new。 getInstance的時候判斷物件是否為空,不是空就直接get. 但是這種方法是執行緒不安全(多執行緒同時訪問時可能會有多個new)

在這裡插入圖片描述

解決方法:getInstance上鎖(synchronized):整個方法加鎖,鎖粒度太粗。因為整個方法都拒絕多執行緒訪問,若方法中含有較多業務程式碼,效率會很低。
在這裡插入圖片描述
解決方法,只對物件為空時的程式碼同步:
在這裡插入圖片描述
但是在一個執行緒判斷物件為空後,獲得鎖之前,可能已經有另外一個執行緒把物件給new出來了。所以有最後的寫法(DCL, double check lock):
在這裡插入圖片描述
判斷兩次INSTANCE是否為空(上鎖前後)

回到問題:DCL中需不需要加volatile?
在這裡插入圖片描述

volatile 的兩個功能:

  1. 執行緒可見性
  2. 禁止指令重排序

回顧一下新建物件時發生的:呼叫構造器初始化和建立連結的兩條指令如果發生重排序會怎麼樣?

執行緒1在連結時會連到一個半初始化的物件上(t不為空了),
執行緒二來判斷是否為空(不為空,已經半初始化了),那麼就直接get了這個半初始化的物件m=0
在這裡插入圖片描述
所以要加 volatile !

第三題:物件在記憶體中的儲存佈局

在這裡插入圖片描述
對齊:前面三個部分加起來的bit數不能被8整除,就補齊
用ClassLayout裡的parseInstance函式將物件儲存佈局列印
在這裡插入圖片描述
執行結果(這裡沒有例項資料 0Bytes):
在這裡插入圖片描述

一共16個位元組

第四題: 物件頭具體包括什麼(mark word classpointer 主要是鎖的資訊)

鎖升級的過程:synchronized 是對物件上鎖而不是對程式碼上鎖
列印輸出上鎖前後的物件儲存佈局:

在這裡插入圖片描述
可以看出markword裡儲存的就是鎖資訊:
對於一個剛剛new出來的物件,先上的是偏向鎖,再是自旋鎖(無鎖,lock-free,輕量級鎖),再是重量級鎖
在這裡插入圖片描述

偏向鎖(biased lock):第一個執行緒來時,只需要貼上名片,因為synchronized是可重入的,又沒有鎖競爭,效率很高
當第二個執行緒來要與第一個執行緒競爭鎖,先把偏向鎖撤銷,然後兩個執行緒用CAS(compare and swap)的方式競爭鎖
在這裡插入圖片描述
在這裡插入圖片描述

CAS的過程:每個執行緒要對這個物件做操作時先讀出來,再計算,如果寫回去的時候檢查它還是原來讀出來的數,那說明這中間沒執行緒動過它,就成功了,如果寫回去的時候發現它已經不是原來那個值了,那就重新讀出來,再計算,再寫回(比較)
在這裡插入圖片描述

這樣就不用加鎖了
但是會有ABA問題,可以用加版本的方式解決ABA問題

第五題:物件怎麼定位

在這裡插入圖片描述

控制代碼方式唯一的優勢:物件小,垃圾回收時不用頻繁改動t
缺點:兩次訪問

第6題:物件怎麼分配:

優先在棧中分配(宣告週期短,不需要GC,效率大概比在堆中分配快一倍)
可以進行棧上分配的條件:1.逃逸分析 DoEscapeAnalysis(沒有別的方法用這個物件)2.可以進行標量替換EliminateAllocation(這個物件可以用在棧中存的成員變數代替)
在這裡插入圖片描述

不能放入棧中的先判斷大不大,大的話直接放入old區老年代
不大的話,判斷是否符合執行緒本地分配(Thread Local Allocat Buffer), 如果符合,那麼放入執行緒自己獨有的一小塊記憶體中,不需要鎖。如果不符合,那麼需要加鎖競爭記憶體,但都是分配進堆中的EDEN區
在這裡插入圖片描述

分代年齡,存在markword中

第7題:一個Object 佔多少位元組

Object o = new Object;
o, 普通物件指標 Ordinary object pointers (OOPS) 佔4個位元組
不考慮o,一個Object也不一定是16個位元組
檢視Java的命令引數
因為在64位的機器上,類指標應該是8位元組,但是由於預設開啟UseCompressedClassPointers
這個指標被壓縮到4位元組,同時例項資料裡如果有普通物件指標,比如成員變數有一個字串或者陣列,這個指標原本也是8位元組,但是預設UseCompressedOops,就壓縮到4位元組
在這裡插入圖片描述
-XX:-UseCompressedClassPointers, 去掉壓縮類指標
在這裡插入圖片描述

那麼什麼情況下不壓縮?
答:4個位元組的指標,能定址的最大空間是多大:32G
如果堆記憶體空間超過32G,壓縮自動不起作用?(這裡沒講清楚)

(文中截圖來源馬士兵老師公開課)