成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

談談 JVM 內部鎖升級過程

開發
對象在內存中的內存布局是什么樣的?談談AQS,為什么AQS底層是CAS+volatile?描述下鎖的四種狀態和鎖升級過程?

 [[409559]]

一 為什么講這個?

總結AQS之后,對這方面順帶的復習一下。本文從以下幾個高頻問題出發:

對象在內存中的內存布局是什么樣的?
描述synchronized和ReentrantLock的底層實現和重入的底層原理。
談談AQS,為什么AQS底層是CAS+volatile?
描述下鎖的四種狀態和鎖升級過程?
Object o = new Object() 在內存中占用多少字節?
自旋鎖是不是一定比重量級鎖效率高?
打開偏向鎖是否效率一定會提升?
重量級鎖到底重在哪里?
重量級鎖什么時候比輕量級鎖效率高,同樣反之呢?

二 加鎖發生了什么?

無意識中用到鎖的情況:

//System.out.println都加了鎖public void println(String x) { synchronized (this) { print(x); newLine(); }}
簡單加鎖發生了什么?

要弄清楚加鎖之后到底發生了什么需要看一下對象創建之后再內存中的布局是個什么樣的?

一個對象在new出來之后在內存中主要分為4個部分:

markword這部分其實就是加鎖的核心,同時還包含的對象的一些生命信息,例如是否GC、經過了幾次Young GC還存活。
klass pointer記錄了指向對象的class文件指針。
instance data記錄了對象里面的變量數據。
padding作為對齊使用,對象在64位服務器版本中,規定對象內存必須要能被8字節整除,如果不能整除,那么就靠對齊來補。舉個例子:new出了一個對象,內存只占用18字節,但是規定要能被8整除,所以padding=6。

知道了這4個部分之后,我們來驗證一下底層。借助于第三方包 JOL = Java Object Layout java內存布局去看看。很簡單的幾行代碼就可以看到內存布局的樣式:

  1. public class JOLDemo { private static Object o; public static void main(String[] args) { o = new Object(); synchronized (o){ System.out.println(ClassLayout.parseInstance(o).toPrintable()); } }} 

將結果打印出來:

從輸出結果看:

1)對象頭包含了12個字節分為3行,其中前2行其實就是markword,第三行就是klass指針。值得注意的是在加鎖前后輸出從001變成了000。Markword用處:8字節(64bit)的頭記錄一些信息,鎖就是修改了markword的內容8字節(64bit)的頭記錄一些信息,鎖就是修改了markword的內容字節(64bit)的頭記錄一些信息。從001無鎖狀態,變成了00輕量級鎖狀態。

2)New出一個object對象,占用16個字節。對象頭占用12字節,由于Object中沒有額外的變量,所以instance = 0,考慮要對象內存大小要被8字節整除,那么padding=4,最后new Object() 內存大小為16字節。

拓展:什么樣的對象會進入老年代?很多場景例如對象太大了可以直接進入,但是這里想探討的是為什么從Young GC的對象最多經歷15次Young GC還存活就會進入Old區(年齡是可以調的,默認是15)。上圖中hotspots的markword的圖中,用了4個bit去表示分代年齡,那么能表示的最大范圍就是0-15。所以這也就是為什么設置新生代的年齡不能超過15,工作中可以通過-XX:MaxTenuringThreshold去調整,但是一般我們不會動。

三 鎖的升級過程

1 鎖的升級驗證

探討鎖的升級之前,先做個實驗。兩份代碼,不同之處在于一個中途讓它睡了5秒,一個沒睡。看看是否有區別。

  1. public class JOLDemo { private static Object o; public static void main(String[] args) { o = new Object(); synchronized (o){ System.out.println(ClassLayout.parseInstance(o).toPrintable()); } }}----------------------------------------------------------------------------------------------public class JOLDemo { private static Object o; public static void main(String[] args) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } o = new Object(); synchronized (o){ System.out.println(ClassLayout.parseInstance(o).toPrintable()); } }} 

這兩份代碼會不會有什么區別?運行之后看看結果:

有點意思的是,讓主線程睡了5s之后輸出的內存布局跟沒睡的輸出結果居然不一樣。

Syn鎖升級之后,jdk1.8版本的一個底層默認設置4s之后偏向鎖開啟。也就是說在4s內是沒有開啟偏向鎖的,加了鎖就直接升級為輕量級鎖了。

那么這里就有幾個問題了?

為什么要進行鎖升級,以前不是默認syn就是重量級鎖么?要么不用要么就用別的不行么?
既然4s內如果加了鎖就直接到輕量級,那么能不能不要偏向鎖,為什么要有偏向鎖?
為什么要設置4s之后開始偏向鎖?
問題1:為什么要進行鎖升級?鎖了就鎖了,不就要加鎖么?

首先明確早起jdk1.2效率非常低。那時候syn就是重量級鎖,申請鎖必須要經過操作系統老大kernel進行系統調用,入隊進行排序操作,操作完之后再返回給用戶態。

內核態:用戶態如果要做一些比較危險的操作直接訪問硬件,很容易把硬件搞死(格式化,訪問網卡,訪問內存干掉、)操作系統為了系統安全分成兩層,用戶態和內核態 。申請鎖資源的時候用戶態要向操作系統老大內核態申請。Jdk1.2的時候用戶需要跟內核態申請鎖,然后內核態還會給用戶態。這個過程是非常消耗時間的,導致早期效率特別低。有些jvm就可以處理的為什么還交給操作系統做去呢?能不能把jvm就可以完成的鎖操作拉取出來提升效率,所以也就有了鎖優化。

問題2:為什么要有偏向鎖?

其實這本質上歸根于一個概率問題,統計表示,在我們日常用的syn鎖過程中70%-80%的情況下,一般都只有一個線程去拿鎖,例如我們常使用的System.out.println、StringBuffer,雖然底層加了syn鎖,但是基本沒有多線程競爭的情況。那么這種情況下,沒有必要升級到輕量級鎖級別了。偏向的意義在于:第一個線程拿到鎖,將自己的線程信息標記在鎖上,下次進來就不需要在拿去拿鎖驗證了。如果超過1個線程去搶鎖,那么偏向鎖就會撤銷,升級為輕量級鎖,其實我認為嚴格意義上來講偏向鎖并不算一把真正的鎖,因為只有一個線程去訪問共享資源的時候才會有偏向鎖這個情況。

無意使用到鎖的場景:

  1. /***StringBuffer內部同步***/public synchronized int length() { return count;} //System.out.println 無意識的使用鎖public void println(String x) { synchronized (this) { print(x); newLine(); } } 

問題3:為什么jdk8要在4s后開啟偏向鎖?

其實這是一個妥協,明確知道在剛開始執行代碼時,一定有好多線程來搶鎖,如果開了偏向鎖效率反而降低,所以上面程序在睡了5s之后偏向鎖才開放。為什么加偏向鎖效率會降低,因為中途多了幾個額外的過程,上了偏向鎖之后多個線程爭搶共享資源的時候要進行鎖升級到輕量級鎖,這個過程還的把偏向鎖進行撤銷在進行升級,所以導致效率會降低。為什么是4s?這是一個統計的時間值。

當然我們是可以禁止偏向鎖的,通過配置參數-XX:-UseBiasedLocking = false來禁用偏向鎖。jdk15之后默認已經禁用了偏向鎖。本文是在jdk8的環境下做的鎖升級驗證。

2 鎖的升級流程

上面已經驗證了對象從創建出來之后進內存從無鎖狀態->偏向鎖(如果開啟了)->輕量級鎖的過程。對于鎖升級的流程繼續往下,輕量級鎖之后就會變成重量級鎖。首先我們先理解什么叫做輕量級鎖,從一個線程搶占資源(偏向鎖)到多線程搶占資源升級為輕量級鎖,線程如果沒那么多的話,其實這里就可以理解為CAS,也就是我們說的Compare and Swap,比較并交換值。在并發編程中最簡單的一個例子就是并發包下面的原子操作類AtomicInteger。在進行類似++操作的時候,底層其實就是CAS鎖。

  1. public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1);}public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5;} 

問題4:什么情況下輕量級鎖要升級為重量級鎖呢?

首先我們可以思考的是多個線程的時候先開啟輕量級鎖,如果它carry不了的情況下才會升級為重量級。那么什么情況下輕量級鎖會carry不住。1、如果線程數太多,比如上來就是10000個,那么這里CAS要轉多久才可能交換值,同時CPU光在這10000個活著的線程中來回切換中就耗費了巨大的資源,這種情況下自然就升級為重量級鎖,直接叫給操作系統入隊管理,那么就算10000個線程那也是處理休眠的情況等待排隊喚醒。

2、CAS如果自旋10次依然沒有獲取到鎖,那么也會升級為重量級。

總的來說2種情況會從輕量級升級為重量級,10次自旋或等待cpu調度的線程數超過cpu核數的一半,自動升級為重量級鎖。看服務器CPU的核數怎么看,輸入top指令,然后按1就可以看到。

問題5:都說syn為重量級鎖,那么到底重在哪里?

JVM偷懶把任何跟線程有關的操作全部交給操作系統去做,例如調度鎖的同步直接交給操作系統去執行,而在操作系統中要執行先要入隊,另外操作系統啟動一個線程時需要消耗很多資源,消耗資源比較重,重就重在這里。

整個鎖升級過程如圖所示:

四 synchronized的底層實現

上面我們對對象的內存布局有了一些了解之后,知道鎖的狀態主要存放在markword里面。這里我們看看底層實現。

  1. public class RnEnterLockDemo { public void method() { synchronized (this) { System.out.println("start"); } }} 

對這段簡單代碼進行反解析看看什么情況。javap -c RnEnterLockDemo.class

首先我們能確定的是syn肯定是還有加鎖的操作,看到的信息中出現了monitorenter和monitorexit,主觀上就可以猜到這是跟加鎖和解鎖相關的指令。有意思的是1個monitorenter和2個monitorexit。為什么呢?正常來說應該就是一個加鎖和一個釋放鎖啊。其實這里也體現了syn和lock的區別。syn是JVM層面的鎖,如果異常了不用自己釋放,jvm會自動幫助釋放,這一步就取決于多出來的那個monitorexit。而lock異常需要我們手動補獲并釋放的。

關于這兩條指令的作用,我們直接參考JVM規范中描述:

monitorenter :
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows: If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor. If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count. If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership

翻譯一下:

每個對象有一個監視器鎖(monitor)。當monitor被占用時就會處于鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的所有權,過程如下:

如果monitor的進入數為0,則該線程進入monitor,然后將進入數設置為1,該線程即為monitor的所有者。
如果線程已經占有該monitor,只是重新進入,則進入monitor的進入數加1。
如果其他線程已經占用了monitor,則該線程進入阻塞狀態,直到monitor的進入數為0,再重新嘗試獲取monitor的所有權。
monitorexit: 
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref. The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

翻譯一下:

執行monitorexit的線程必須是objectref所對應的monitor的所有者。指令執行時,monitor的進入數減1,如果減1后進入數為0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去獲取這個 monitor的所有權。

通過這段話的描述,很清楚的看出Synchronized的實現原理,Synchronized底層通過一個monitor的對象來完成,wait/notify等方法其實也依賴于monitor對象,這就是為什么只有在同步的塊或者方法中才能調用wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException的異常。

每個鎖對象擁有一個鎖計數器和一個指向持有該鎖的線程的指針。

當執行monitorenter時,如果目標對象的計數器為零,那么說明它沒有被其他線程所持有,Java虛擬機會將該鎖對象的持有線程設置為當前線程,并且將其計數器加i。在目標鎖對象的計數器不為零的情況下,如果鎖對象的持有線程是當前線程,那么Java虛擬機可以將其計數器加1,否則需要等待,直至持有線程釋放該鎖。當執行monitorexit時,Java虛擬機則需將鎖對象的計數器減1。計數器為零代表鎖已被釋放。

總結

以往的經驗中,只要用到synchronized就以為它已經成為了重量級鎖。在jdk1.2之前確實如此,后來發現太重了,消耗了太多操作系統資源,所以對synchronized進行了優化。以后可以直接用,至于鎖的力度如何,JVM底層已經做好了我們直接用就行。

最后再看看開頭的幾個問題,是不是都理解了呢。帶著問題去研究,往往會更加清晰。希望對大家有所幫助。
 

責任編輯:梁菲 來源: 阿里云云棲號
相關推薦

2021-07-01 19:30:23

JVM內部鎖線程

2022-03-08 08:44:13

偏向鎖Java內置鎖

2021-03-31 10:05:26

偏向鎖輕量級鎖

2023-11-08 08:18:19

鎖升級多線程

2024-06-27 08:55:41

2023-10-07 08:41:42

JavaJVM

2024-04-19 08:05:26

鎖升級Java虛擬機

2012-03-01 10:51:37

JavaJVM

2011-06-17 17:37:16

JavaSQL Server

2020-10-19 09:09:46

Class文件加載過程

2011-11-28 12:31:20

JavaJVM

2024-12-03 00:35:20

2010-09-26 16:14:22

JVM實現機制JVM

2020-12-30 09:18:46

JVM內部信息

2020-02-24 21:43:36

avaJVM 級鎖線程安全

2011-11-28 12:55:37

JavaJVM

2019-08-13 15:01:04

變更運維項目經理

2024-08-13 14:08:25

2009-07-08 15:11:58

JVM GC調整優化

2021-03-11 08:10:48

JVM對象的創建School
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 精品国产91乱码一区二区三区 | 色呦呦在线 | 在线免费观看黄网 | 人人射人人 | caoporn免费在线视频 | 日韩久久网 | 中文字幕在线免费视频 | 欧美在线天堂 | 欧美一区二区三区大片 | 国产在线对白 | 日韩欧美亚洲 | 国产99久久精品一区二区300 | 精品一区二区三区四区在线 | 日韩 欧美 二区 | 午夜影院在线观看 | 激情的网站 | 噜啊噜在线 | 波霸ol一区二区 | 在线视频一区二区 | 亚洲欧美日韩在线不卡 | 在线色网站 | 久久99国产精一区二区三区 | 亚洲国产精品一区二区第一页 | 狠狠干美女 | 成人在线视频观看 | 国产日韩一区二区三区 | 精品免费国产一区二区三区四区介绍 | 欧美日韩不卡合集视频 | 一区二区三区四区在线 | 天天综合网天天综合 | 国产精品自产拍 | 精品国产视频 | 亚洲福利一区二区 | 欧美自拍另类 | 欧美一级精品片在线看 | 国产97色| 久久99精品久久久 | 亚洲毛片在线观看 | 国产欧美一区二区三区久久人妖 | 成人免费在线观看 | 色视频网站免费 |