Java對(duì)象頭(64位虛擬機(jī))
- 整個(gè)對(duì)象頭一共有128位,Mark Word有64位近刘,Klass Word有64位宣脉,但是Klass Word因?yàn)橹羔槈嚎s的原因被壓縮為32位,使用對(duì)象頭一共有96位瓷患,而且現(xiàn)在我們更需要關(guān)注的是前64位。至于Klass Word指向了方法區(qū)的模板類遣妥,字節(jié)碼擅编。
- 對(duì)象頭還不是一成不變的,就表格可以看出箫踩,對(duì)象的狀態(tài)會(huì)改變對(duì)象頭的數(shù)值爱态,這里我們分為5個(gè)狀態(tài),分別是無(wú)鎖(001)境钟、偏向鎖(101)锦担、輕量鎖(00)、重量鎖(10)和被gc標(biāo)記(11)的對(duì)象慨削。
- 64位虛擬機(jī)對(duì)象頭.png
對(duì)象頭占據(jù)內(nèi)存
- 例如:int是4個(gè)字節(jié)洞渔,Integer對(duì)象的Mark Word8字節(jié),Klass Word8個(gè)字節(jié)(指針壓縮后4字節(jié))缚态,存儲(chǔ)占4字節(jié)磁椒,存儲(chǔ)一個(gè)數(shù)需要占8(Mark Word)+4(Klass Word)+4(存儲(chǔ)占4字節(jié))=16字節(jié)
驗(yàn)證new Integer(1)占據(jù)內(nèi)存大小
<!-- 查看對(duì)象偏向鎖相關(guān)信息 -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
@Slf4j(topic = "ants.TestIntegerSize")
public class TestIntegerSize {
public static void main(String[] args) {
Integer i = new Integer(1);
log.debug(ClassLayout.parseInstance(i).toPrintable());
}
}
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
2020-04-12 17:45:45.202 [main] DEBUG ants.TestIntegerSize 16 - java.lang.Integer object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) ae 22 00 f8 (10101110 00100010 00000000 11111000) (-134208850)
12 4 int Integer.value 1
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
- 由上面控制臺(tái)結(jié)果分析,new Integer(1)會(huì)占據(jù)16字節(jié)玫芦,而我們知道int i=1只會(huì)占四個(gè)字節(jié)浆熔。
Monitor(鎖) 監(jiān)視器|管程
-
每個(gè)Java對(duì)象都可以關(guān)聯(lián)一個(gè)Monitor對(duì)象,如果使用synchronized給對(duì)象上鎖(重量級(jí)鎖)之后桥帆,該對(duì)象頭的Mark Word就被設(shè)置指向Monitor對(duì)象指針医增。
線程-對(duì)象-Monitor關(guān)系.png
- synchronized(obj)使用對(duì)象鎖(obj)和Monitor對(duì)象一一對(duì)應(yīng)
- 剛開(kāi)始Monitor中Owner為null
- 當(dāng)Thread-2執(zhí)行synchronized就會(huì)將Monitor的所有者Owner置為Thread-2慎皱,Monitor中只能有一個(gè)Owner
- Thread在上鎖過(guò)程中,如果Thread-3叶骨,thread-4茫多,thread-5也來(lái)執(zhí)行synchronized(obj),就會(huì)進(jìn)入EntryList中進(jìn)阻塞(BLOCKED)狀態(tài)
- Thread-2執(zhí)行完同步代碼塊內(nèi)容邓萨,然后喚醒EntryList中等待的線程來(lái)競(jìng)爭(zhēng)鎖地梨,競(jìng)爭(zhēng)時(shí)是非公平的(synchronized沒(méi)有提供公平鎖,非公平是指如果Thread-2釋放鎖的時(shí)候來(lái)了Thread-6缔恳,那么Thread-3和Thread-6將同時(shí)有機(jī)會(huì)獲得obj對(duì)象)
- 圖中Thread-0宝剖,Therad-1是之前獲得過(guò)鎖,但是條件不滿足進(jìn)入WAITING狀態(tài)的線程(跟wait-notify相關(guān))
注意
- synchronized必須是關(guān)聯(lián)統(tǒng)一個(gè)對(duì)象時(shí)歉甚,才會(huì)出現(xiàn)上面結(jié)果
- 不加synchronized不會(huì)關(guān)聯(lián)monitor監(jiān)視器万细,不會(huì)出現(xiàn)上面結(jié)果
synchronized原理
- dup:將lock復(fù)制一份
- Exception table:檢測(cè)一定范圍,如果范圍內(nèi)出現(xiàn)異常做處理
- 6-16-19-any纸泄,表示如果在6-6行出現(xiàn)異常赖钞,java會(huì)在19行開(kāi)始執(zhí)行,同樣保存lock引用聘裁,將對(duì)象頭重置雪营,喚醒EntryList,讓其他線程競(jìng)爭(zhēng)鎖衡便,最后將處理不了的異常拋出去
- 在字節(jié)碼中可以看出献起,即使synchronized中出現(xiàn)異常,synchronized也會(huì)釋放鎖
不同鎖
- Monitor
- 輕量級(jí)鎖
- 偏向鎖
- 批量重偏向(一個(gè)類的偏向鎖撤銷達(dá)到20閾值)
- 一個(gè)類的偏向鎖撤銷達(dá)到40閾值镣陕,對(duì)象升級(jí)為輕量級(jí)鎖
輕量級(jí)鎖
- 使用場(chǎng)景:如果一個(gè)對(duì)象雖然有多線程訪問(wèn)谴餐,單多線程訪問(wèn)的時(shí)間是錯(cuò)開(kāi)的(沒(méi)有線程競(jìng)爭(zhēng)),name可以用輕量級(jí)鎖來(lái)優(yōu)化呆抑。
- 語(yǔ)法:輕量級(jí)鎖對(duì)使用這透明岂嗓,仍然使用synchronized
輕量級(jí)鎖加鎖、解鎖流程
public class LightLock {
static final Object object = new Object();
public static void method1(){
synchronized (object){
// 同步代碼塊1
method2();
}
}
public static void method2(){
synchronized (object){
// 同步代碼塊2
}
}
}
-
創(chuàng)建鎖記錄(Lock Record)對(duì)象鹊碍,每個(gè)線程的棧幀都會(huì)包含一個(gè)鎖記錄厌殉,內(nèi)部可以存儲(chǔ)鎖定對(duì)象的Mark Word
image.png -
讓鎖記錄中Object Reference指向鎖對(duì)象,并嘗試使用cas替換Object的Mark Word的值存入鎖記錄
image.png -
如果替換成功侈咕,對(duì)象頭中存儲(chǔ)了鎖記錄地址和狀態(tài)00(代表輕量級(jí)鎖)年枕,表示該線程給對(duì)象加鎖,--(object對(duì)象頭中Mark Word發(fā)生變化乎完,原先01無(wú)鎖狀態(tài)變成00輕量級(jí)鎖狀態(tài)熏兄,另外分代年齡等信息變?yōu)殒i記錄地址),--(鎖記錄里面將記錄hash碼、分代年齡等信息)(--鎖記錄和Mark Word信息互換)
image.png -
如果cas失敗,分為兩種情況
4.1 如果是其他線程已經(jīng)持有了該Object的輕量級(jí)鎖摩桶,這時(shí)表名有競(jìng)爭(zhēng)桥状,進(jìn)入鎖膨脹過(guò)程
4.2 如果是自己執(zhí)行了synchronized表示鎖重入,那么再添加一條Lock Record作為重入的計(jì)數(shù)
image.png -
當(dāng)退出synchronized代碼塊(解鎖時(shí))硝清,如果取值為null的鎖記錄辅斟,這時(shí)重置鎖記錄,表示重入計(jì)數(shù)減一
image.png - 當(dāng)退出synchronized代碼塊(解鎖時(shí))芦拿,如果取值不為null的鎖記錄士飒,這時(shí)cas將Mark Word的值恢復(fù)給對(duì)象頭
- 成功,則解鎖成功
- 失敗蔗崎,說(shuō)明輕量級(jí)鎖進(jìn)程鎖膨脹已經(jīng)升級(jí)為重量級(jí)鎖酵幕,進(jìn)入重量級(jí)鎖的解鎖流程
重量級(jí)鎖加鎖、解鎖流程 - 如果在嘗試加輕量級(jí)鎖的過(guò)程中缓苛,cas操作無(wú)法成功芳撒,這時(shí)一種情況是其他線程為此對(duì)象加上了輕量級(jí)鎖(有競(jìng)爭(zhēng)),這時(shí)需要進(jìn)行鎖膨脹未桥,將輕量級(jí)鎖升級(jí)為重量級(jí)鎖
public class WeightLock {
static final Object object = new Object();
public static void method1(){
synchronized (object){
// 同步代碼塊1
}
}
}
-
當(dāng)Thread-1進(jìn)入輕量級(jí)鎖加鎖時(shí)笔刹,Thread-0已經(jīng)對(duì)該對(duì)象加了輕量級(jí)鎖
image.png -
這時(shí)Thread-1加鎖失敗,進(jìn)入鎖膨脹流程
2.1 為Object對(duì)象申請(qǐng)Monitor鎖冬耿,讓Object指向重量級(jí)鎖地址
2.2 然后線程Thread-1進(jìn)入Monitor的EntryList阻塞(BLOCKED)狀態(tài)
image.png - 當(dāng)Thread-0退出同步代碼塊解鎖時(shí)舌菜,使用cas將Mark Word的值恢復(fù)給對(duì)象頭,失敗亦镶。這時(shí)會(huì)進(jìn)入重量級(jí)解鎖流程日月,即按照Monitor地址找到Monitor對(duì)象,設(shè)置Owner為null染乌,喚醒EntryList中的BLOCKED線程
重量級(jí)鎖競(jìng)爭(zhēng)時(shí)自旋優(yōu)化
- 重量級(jí)鎖競(jìng)爭(zhēng)時(shí)候山孔,可以通過(guò)自旋來(lái)進(jìn)行優(yōu)化懂讯,如果當(dāng)前線程自旋成功(即這時(shí)保持鎖的新城已經(jīng)退出了同步代碼塊荷憋。釋放了鎖),這時(shí)當(dāng)前線程就可以避免阻塞
- 以犧牲CPU褐望,多核CPU情況才能發(fā)揮優(yōu)勢(shì)
- Java6之后自旋是自適應(yīng)的勒庄,比如對(duì)象剛剛一次自旋成功過(guò),就認(rèn)為自旋成功可能性大瘫里,就多自旋幾次实蔽;反之少自旋甚至不自旋。
- Java7之后不能控制是否開(kāi)啟自旋功能
- 自旋失敗后谨读,進(jìn)入EntryList阻塞(BLOCKED)狀態(tài)
自旋重試成功.png
偏向鎖優(yōu)化 - 輕量級(jí)鎖不需要Monitor對(duì)象局装,使用線程棧幀中的鎖記錄充當(dāng)輕量級(jí)鎖
-
輕量級(jí)鎖在發(fā)生鎖重入的時(shí)候,是需要cas操作,插入一條值為null的鎖記錄
image.png -
Java6引入偏向鎖:只有第一次使用cas將線程id設(shè)置為對(duì)象頭的Mark Word頭铐尚,之后發(fā)現(xiàn)這個(gè)線程di是自己的就表示沒(méi)有競(jìng)爭(zhēng)拨脉,不用重新cas操作。以后只要不發(fā)生競(jìng)爭(zhēng)宣增,這個(gè)對(duì)象就歸該線程所有
image.png - 當(dāng)創(chuàng)建一個(gè)對(duì)象時(shí):
- 如果開(kāi)啟了偏向鎖(默認(rèn)開(kāi)啟)玫膀,那么對(duì)象創(chuàng)建后,markword值最后三位是101爹脾,這是它的thread帖旨、epoch、age都為0
- 偏向鎖默認(rèn)是延遲的灵妨,不會(huì)再程序啟動(dòng)時(shí)立即生效解阅,如果想避免延遲,可以加-XX:BiasedLockingStartupDelay=0來(lái)禁用延遲
- 如果沒(méi)有開(kāi)啟偏向鎖闷串,那么對(duì)象創(chuàng)建后瓮钥,markword值最后3位為001,這時(shí)它的hashcode烹吵、age都為0碉熄,第一次用到hashcode時(shí)才會(huì)賦值
驗(yàn)證偏向鎖
/**
* @author magw
* @version 1.0
* @date 2020/4/12 下午3:40
* @description: No Description
* 偏向鎖+對(duì)象內(nèi)存大小
1).大端存儲(chǔ):大端模式,是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的低地址中肋拔,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的高地址中锈津,這樣的存儲(chǔ)模式有點(diǎn)兒類似于把數(shù)據(jù)當(dāng)作字符串順序處理:地址由小向大增加,而數(shù)據(jù)從高位往低位放凉蜂。
2).小端存儲(chǔ):小端模式琼梆,是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的高地址中,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的低地址中窿吩,這種存儲(chǔ)模式將地址的高低和數(shù)據(jù)位權(quán)有效地結(jié)合起來(lái)茎杂,高地址部分權(quán)值高,低地址部分權(quán)值低纫雁,和我們的邏輯方法一致煌往。
*/
@Slf4j(topic = "ants.TestBiased")
public class TestBiased {
public static void main(String[] args) {
Object i = new Object();
System.out.println(Integer.toHexString(i.hashCode()));
System.out.println(ClassLayout.parseInstance(i).toPrintable());
}
}
2280cdac
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 ac cd 80 (00000001 10101100 11001101 10000000) (-2134004735)
4 4 (object header) 22 00 00 00 (00100010 00000000 00000000 00000000) (34)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
-
從控制臺(tái)看,hacode(2280cdac)和對(duì)象頭(accd8022)hashcode(對(duì)象頭25-56)內(nèi)容不符合轧邪,這是因?yàn)槲译娔X是小端存儲(chǔ)刽脖,即數(shù)據(jù)低地址存放數(shù)據(jù)地位,應(yīng)該反來(lái)讀取忌愚,即(2280cdac)曲管,這時(shí)對(duì)象頭和hashcode對(duì)應(yīng)起來(lái)
image.png - 但是我們發(fā)現(xiàn)對(duì)象頭最低3位是001,代表是正常狀態(tài)硕糊,并不是偏向鎖狀態(tài)院水?這時(shí)因?yàn)槠蜴i默認(rèn)是延遲的腊徙,可以采取兩種方式進(jìn)行來(lái)顯示出對(duì)象頭中的偏向鎖狀態(tài)
2.1 程序啟動(dòng)時(shí),在對(duì)象new之前睡眠>=4秒檬某,例如Thread.sleep(5000);
2.2 設(shè)置vm啟動(dòng)參數(shù),-XX:BiasedLockingStartupDelay=0
2.3 關(guān)閉偏向鎖:-XX:-UseBiasedLocking - 線程在修改完對(duì)象頭中信息后昧穿,如果再次訪問(wèn),對(duì)象頭中的線程id不會(huì)變
public class TestBiased {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);
Object i = new Object();
System.out.println(ClassLayout.parseInstance(i).toPrintable());
}
}
撤銷偏向鎖
- 如果調(diào)用鎖對(duì)象的hashcode()方法橙喘,會(huì)撤銷對(duì)象偏向鎖时鸵,如果遇到同步,直接升級(jí)為輕量級(jí)鎖厅瞎。調(diào)用完hashcode后饰潜,線程對(duì)象頭沒(méi)有地址存放hashcode值。輕量級(jí)鎖調(diào)用hashcode后會(huì)將hashcode保存到鎖記錄里和簸,重量級(jí)鎖調(diào)用hashcode彭雾,會(huì)將hashcode保存到moitor里。
- 其他對(duì)象使用擁有偏向鎖對(duì)象(兩個(gè)線程交叉執(zhí)行)锁保,偏向鎖會(huì)升級(jí)輕量級(jí)鎖薯酝,當(dāng)代碼執(zhí)行后,對(duì)象的偏向鎖會(huì)撤銷
- 調(diào)用wait/notify后爽柒,也會(huì)撤銷偏向鎖
@Slf4j(topic = "ants.CannelBiasedMethod")
public class CannelBiasedMethod {
public static void main(String[] args) {
Object o = new Object();
new Thread("t1") {
@Override
public void run() {
log.debug(ClassLayout.parseInstance(o).toPrintable());
synchronized (o){
log.debug(ClassLayout.parseInstance(o).toPrintable());
}
log.debug(ClassLayout.parseInstance(o).toPrintable());
synchronized (CannelBiasedMethod.class){
CannelBiasedMethod.class.notify();
}
}
}.start();
new Thread("t2") {
@SneakyThrows
@Override
public void run() {
synchronized (CannelBiasedMethod.class){
CannelBiasedMethod.class.wait();
}
log.debug(ClassLayout.parseInstance(o).toPrintable());
synchronized (o){
log.debug(ClassLayout.parseInstance(o).toPrintable());
}
log.debug(ClassLayout.parseInstance(o).toPrintable());
}
}.start();
}
}
批量重偏向
- 如果對(duì)象雖然被多個(gè)線程訪問(wèn)吴菠,但是沒(méi)有競(jìng)爭(zhēng),這是偏向了線程t1的對(duì)象仍有機(jī)會(huì)重新偏向t2浩村,重偏向會(huì)重置對(duì)象的Thread ID
- 當(dāng)撤銷偏向鎖閾值超過(guò)20次后做葵,jvm會(huì)在給這些對(duì)象加鎖時(shí)重新偏向至加鎖線程
- 線程1給對(duì)象線程id修改,t2線程獲得對(duì)象的線程id不變心墅,t2在執(zhí)行同步代碼塊時(shí)酿矢,鎖升級(jí)為輕量級(jí)鎖,同步代碼塊執(zhí)行完怎燥,對(duì)象變成無(wú)鎖狀態(tài)
- 在達(dá)到20次后瘫筐,后面的對(duì)象中的線程id就會(huì)被修改,不會(huì)再偏向鎖-輕量級(jí)鎖-無(wú)鎖狀態(tài)轉(zhuǎn)化
//查看t2線程執(zhí)行的執(zhí)行的第19和20次铐姚,發(fā)現(xiàn)線程id變化策肝,在t2后面的線程id,不在會(huì)變化
@Slf4j(topic = "ants.BatchBiased")
public class BatchBiased {
static Thread t1,t2;
public static void main(String[] args) {
int count = 39;
Vector<Object> vector = new Vector<>();
t1 = new Thread("t1"){
@Override
public void run() {
for (int i = 0; i < count; i++) {
Object o = new Object();
vector.add(o);
synchronized (o) {
log.debug(i+"\t-----------"+ClassLayout.parseInstance(o).toPrintable());
}
}
LockSupport.unpark(t2);
}
};
t1.start();
t2 = new Thread("t2"){
@Override
public void run() {
LockSupport.park();
for (int i = 0; i < count; i++) {
Object o =vector.get(i);
log.debug(i+"\t"+"before "+ClassLayout.parseInstance(o).toPrintable());
synchronized (o) {
log.debug(i+"\t"+ClassLayout.parseInstance(o).toPrintable());
}
log.debug(i+"\t"+"after "+ClassLayout.parseInstance(o).toPrintable());
}
}
};
t2.start();
}
}
批量撤銷
- 當(dāng)撤銷偏向鎖超過(guò)40次后谦屑,類再新建的都是不可偏向的對(duì)象驳糯。
鎖消除 - JVM會(huì)在JIT字節(jié)碼進(jìn)一步優(yōu)化篇梭,會(huì)在字節(jié)碼層面優(yōu)化氢橙。會(huì)優(yōu)化掉部分代碼。
- xx:-EliminateLocks,如果能確認(rèn)某個(gè)加鎖的對(duì)象不會(huì)逃逸出局部作用域恬偷,就可以進(jìn)行鎖刪除悍手。.