天天看點

《深入了解 Java 虛拟機》筆記整理

《深入了解 Java 虛拟機》筆記整理

正文

一、Java 記憶體區域與記憶體溢出異常

1、運作時資料區域

程式計數器:目前線程所執行的位元組碼的行号訓示器。線程私有。

Java 虛拟機棧:Java 方法執行的記憶體模型。線程私有。

本地方法棧:Native 方法執行的記憶體模型。線程私有。

Java 堆:存放對象執行個體。分為新生代(Eden 空間、From Survivor 空間、To Survivor 空間)和老年代。線程共享。

方法區:存儲已被虛拟機加載的類資訊、常量、靜态變量、即時編譯器編譯後的代碼等資料。也稱為“永久代”。線程共享。

運作時常量池:方法區的一部分,用于存放編譯期生成的各種字面量和符号引用。 線程共享。

直接記憶體。

2、對象的建立

類加載檢查 -> 配置設定記憶體 -> 初始化零值 -> 設定對象頭 -> 執行 init 方法。

類加載檢查:檢查 new 指令的參數能否在常量池中定位到一個類的符号引用,以及這個符号引用代表的類是否已被加載、解析和初始化過。

配置設定記憶體:把一塊确定大小的記憶體從 Java 堆中劃分出來。

初始化零值:将配置設定到的記憶體空間初始化為零值(不包括對象頭)。

設定對象頭:虛拟機需要對對象進行必要的設定,這些資訊存放在對象的對象頭中。

執行 init 方法:把對象按照程式員的意願進行初始化。

3、對象的記憶體布局

對象頭:

Mark Word:存儲對象自身的運作時資料。

類型指針:存儲對象的類中繼資料的指針。

執行個體資料:對象真正存儲的有效資訊,也是在程式代碼中所定義的各種類型的字段内容。

對齊填充:僅僅起着占位符的作用。

4、對象的通路定位

句柄:引用中存儲的是對象的句柄位址。Java 堆中劃分出一塊記憶體作為句柄池,句柄中包含了對象執行個體資料、類型資料兩者的具體位址資訊。

直接指針:引用中存儲的直接就是對象的位址。

5、OutOfMemoryError 異常

Java 堆溢出。

虛拟機棧和本地方法棧溢出。

方法區和運作時常量池溢出。

本機直接記憶體溢出。

二、垃圾收集器與記憶體配置設定政策

1、判斷對象是否可用

引用計數算法:給對象添加一個引用計數器,每當有一個地方引用它時,計數器值加 1;當引用失效時,計數器值減 1;任何時刻計數器為 0 的對象就是不可能再被使用的。

可達性分析算法:通過一系列被稱為“GC Roots”的對象作為起點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鍊,當一個對象到 GC Roots 沒有任何引用鍊相連時,則此對象不可用。

2、四種引用

強引用:類似“Object obj = new Object()”的引用。隻要強引用還存在,對象就永遠不會回收。

軟引用:用來描述一些還有用但并非必需的對象。記憶體不足時,對象有可能被回收。

弱引用:用來描述非必需的對象,但強度比軟引用弱。GC時,無論記憶體是否足夠,對象都會被回收。

虛引用:也稱幽靈引用或幻影引用,虛引用不會對對象的生存時間構成影響。虛引用的唯一作用就是能在對象被回收時收到一個系統通知。

3、垃圾收集算法

标記-清除算法:分為“标記”和“清除”兩個階段。首先标記出所有需要回收的對象,然後再統一回收所有被标記的對象。會産生大量不連續的記憶體碎片。

複制算法:将可用記憶體按容量劃分為大小相等的兩塊,每次隻使用其中一塊。當一塊記憶體用完時,就将還存活的對象複制到另一塊,然後再把已使用過的記憶體空間一次清理掉。

标記-整理算法:首先标記出所有需要回收的對象,然後将所有存活對象向一端移動,最後直接清理掉端邊界以外的記憶體。

分代收集算法:根據對象存活周期的不同,将 Java 堆劃分為新生代和老年代,然後根據各個年代的特點采用最适當的收集算法。

新生代:采用複制算法。

老年代:采用“标記-清除”或“标記-整理”算法。

4、垃圾收集器

Serial 收集器:單線程。新生代收集器。

ParNew 收集器:Serial 收集器的多線程版本。新生代收集器。

Parallel Scavenge 收集器:多線程。新生代收集器。關注吞吐量。

Serial Old 收集器:Serial 收集器的老年代版本。單線程。使用“标記-整理”算法。

Parallel Old 收集器:Parallel Scavenge 收集器的老年代版本。多線程。使用“标記-整理”算法。

CMS 收集器:并發收集器。使用“标記-清除”算法。關注點是如何縮短垃圾收集時使用者線程的停頓時間。

G1 收集器:面向服務端應用。并行與并發、分代收集、空間整合、可預測停頓時間。

5、記憶體配置設定與回收政策

對象優先在 Eden 配置設定。

大對象直接進入老年代。

長期存活的對象進入老年代。

動态對象年齡判定。

空間配置設定擔保。

三、虛拟機性能監控與故障處理工具

1、JDK 的指令行工具

jps:顯示正在運作的虛拟機程序。常用指令:jps -l。

jstat:監視虛拟機各種運作狀态資訊。常用指令:jstat -gcutil 。

jinfo:顯示虛拟機配置資訊。常用指令:jinfo -flags 。

jmap:主要用于生成堆轉儲快照。常用指令:jmap -dump:format=b,file=。

jhat:分析 jmap 生成的堆轉儲快照。常用指令:jhat 。

jstack:顯示虛拟機目前時刻的線程堆棧資訊。常用指令:jstack -l 。

2、JDK 的可視化工具

JConsole:Java 監視與管理控制台。

VisualVM:多合一故障處理工具。

四、類檔案結構

1、無關性的基石

各種不同平台的虛拟機。

所有平台都統一使用的位元組碼存儲格式。

2、Class 類檔案的結構

(1)Class 檔案的資料類型

無符号數:基本資料類型,以 u1、u2、u4、u8 來分别代表 1 個位元組、2 個位元組、4 個位元組和 8 個位元組的無符号數。用于描述數字、索引引用、數量值或按照 UTF-8 編碼構成字元串值。

表:由多個無符号數或其他表作為資料項構成的複合資料類型,所有表都習慣性地以“_info”結尾。用于描述有層次關系的複合結構資料,整個 Class 檔案本質上就是一張表。

(2)Class 檔案格式

類型 名稱 數量

u4 magic(魔數) 1

u2 minor_version(次版本号) 1

u2 major_version(主版本号) 1

u2 constant_pool_count(常量池容量計數器) 1

cp_info constant_pool(常量池) constant_pool_count - 1

u2 access_flags(通路标志) 1

u2 this_class(類索引) 1

u2 super_class(父類索引) 1

u2 interfaces_count(接口計數器) 1

u2 interfaces(接口索引集合) interfaces_count

u2 fields_count(字段表計數器) 1

field_info fields(字段表集合) fields_count

u2 methods_count(方法表計數器) 1

method_info methods(方法表集合) methods_count

u2 attributes_count(屬性表計數器) 1

attribute_info attributes(屬性表集合) attributes_count

魔數:Class 檔案的頭 4 個位元組,用于确定該檔案是否為 Class 檔案。其值為:0xCAFEBABE(咖啡寶貝?)。

Class 檔案的版本:第 5、6 個位元組是次版本号,第 7、8 個位元組是主版本号。

常量池:可以了解為 Class 檔案中的資源倉庫。主要存放字面量和符号引用。每一項常量都是一個表。

通路标志:用于識别一些類或接口層次的通路資訊,包括:這個 Class 是類還是接口、是否定義為 public、是否定義為 abstract、是否聲明為 final(隻有類可設定)等。

類索引、父類索引與接口索引集合:Class 檔案由這三項資料确定這個類的繼承關系。

字段表集合:用于描述接口或類中聲明的變量。包括類變量和執行個體變量,但不包括在方法内部聲明的局部變量。

方法表集合:用于描述接口或類中聲明的方法。

屬性表集合:在 Class 檔案、字段表、方法表都可以攜帶自己的屬性表集合,以用于描述某些場景專有的資訊。

3、位元組碼指令簡介

加載和存儲指令:用于将資料在棧幀中的局部變量表和操作數棧之間來回傳輸。

運算指令:用于對兩個操作數以上的值進行某種特定運算,并把結果重新存入到操作數棧頂。

類型轉換指令:将兩種不同的數值類型進行互相轉換。

對象建立與通路指令。

操作數棧管理指令:用于直接操作操作數棧。

控制轉移指令:讓 Java 虛拟機有條件或無條件地從指定位置的指令繼續執行程式,而不是從控制轉移指令的下一條指令繼續執行程式。可認為控制轉移指令就是在有條件或無條件地修改 PC 寄存器的值。

方法調用和傳回指令。

異常處理指令。

同步指令:支援方法級的同步和方法内部一段指令序列的同步。

五、虛拟機類加載機制

1、類加載的過程

加載 -> 連接配接(驗證、準備、解析) -> 初始化。

加載:擷取二進制位元組流,并在記憶體中生成一個代表這個類的 java.lang.Class 對象,作為方法區這個類的各種資料的通路入口。

驗證:確定 Class 檔案的位元組流中包含的資訊符合目前虛拟機的要求,并且不會危害虛拟機自身的安全。

檔案格式驗證:驗證位元組流是否符合 Class 檔案格式的規範,并且能被目前版本的虛拟機處理。

中繼資料驗證:對位元組碼描述的資訊進行語義分析,以保證其描述的資訊符合 Java 語言規範的要求。

位元組碼驗證:通過資料流和控制流分析,确定程式語義是合法的、符合邏輯的。

符号引用驗證:對符合引用進行比對性校驗,確定解析動作能正常執行。

準備:為類變量配置設定記憶體并設定初始值。

解析:将常量池内的符号引用替換為直接引用。

初始化:根據程式員的主觀計劃去初始化類變量和其他資源。

2、類加載器

啟動類加載器:負責将存放在 lib 目錄的,或者 -Xbootclasspath 參數所指定路徑中的,能被虛拟機識别的類庫加載到虛拟機記憶體中。

擴充類加載器:負責加載 libext 目錄中的,或者 java.ext.dirs 系統變量所指定路徑中的所有類庫。

應用程式類加載器:負責加載使用者類路徑上所指定的類庫。

3、雙親委派模型

如果一個類加載器收到類加載的請求,它會先把這個請求委派給父加載器去完成,而不會自己去嘗試加載這個類。隻有父加載器無法完成這個加載請求時,子加載器才會嘗試自己去加載。

六、虛拟機位元組碼執行引擎

1、運作時棧幀結構

棧幀是用于支援虛拟機進行方法調用和方法執行的資料結構。棧幀存儲了方法的局部變量表、操作數棧、動态連接配接、方法傳回位址和一些額外的附加資訊。每一個方法從調用開始至執行完成的過程,都對應着一個棧幀在虛拟機裡面從入棧到出棧的過程。

局部變量表:是一組變量值存儲空間,用于存放方法參數和方法内部定義的局部變量。

操作數棧:也稱為操作棧,它是一個後入先出的棧。操作數棧的每一個元素可以是任意的 Java 資料類型。

動态連接配接:每個棧幀都包含一個指向運作時常量池中,該棧幀所屬方法的引用,持有這個引用是為了支援方法調用過程中的動态連接配接。

方法傳回位址:方法退出後需要傳回到方法被調用的位置,程式才能繼續執行。

附加資訊:虛拟機規範允許具體的虛拟機實作增加一些規範裡沒有描述的資訊到棧幀中,例如與調試相關的資訊。

2、方法調用

方法調用并不等于方法執行,方法調用階段唯一的任務就是确定被調用方法的版本(即調用哪一個方法)。此時,在 Class 檔案裡存儲的隻是符号引用,而不是直接引用,隻有在類加載期間,甚至是運作期間才能确定目标方法的直接引用。

解析:在類加載的解析階段,将方法的符号引用轉化為直接引用,這類方法調用稱為解析。這種解析能成立的前提是:方法在程式執行之前有一個可确定的調用版本,并且這個方法的調用版本在運作期不可改變,即“編譯期可知,運作期不可變”。

分派:

靜态分派:在編譯期依賴靜态類型(又稱外觀類型)來定位方法執行版本的分派動作,稱為靜态分派。靜态分派的典型應用是方法重載。

動态分派:在運作期根據實際類型确定方法執行版本的分派過程,稱為動态分派。動态分派的典型應用是方法重寫。

七、早期(編譯期)優化

1、Javac 編譯過程

(1)解析與填充符号表

詞法分析:将源代碼的字元流轉變為标記(Token)集合,标記是編譯過程的最小元素,關鍵字、變量名、字面量、運算符都可以成為标記。

文法分析:根據 Token 序列構造抽象文法樹。

填充符号表:符号表是由一組符号位址和符号資訊構成的表格,可以把它想象成哈希表中 K-V 值對的形式。

(2)注解處理

在編譯期間對注解進行處理。可以讀取、修改、添加抽象文法樹中的任何元素。

(3)語義分析與位元組碼生成

語義分析:對結構上正确的源程式進行上下文邏輯審查。

标注檢查:包括變量使用前是否已被聲明、變量與指派之間的資料類型是否能夠比對等。

資料及控制流分析:對程式上下文邏輯進行更進一步的驗證,包括局部變量在使用前是否有指派、方法的每條路徑是否都有傳回值、是否所有的受查異常都被正确處理等。

解文法糖:虛拟機運作時并不支援文法糖的文法,是以,需要在編譯階段還原回簡單的基礎文法結構。

位元組碼生成:把前面各個步驟所生成的資訊(文法樹、符号表)轉化成位元組碼寫到磁盤中,同時還進行了少量的代碼添加和轉換工作。

2、Java 文法糖

泛型與類型擦除:泛型的本質是參數化類型的應用,即将所操作的資料類型指定為一個參數。

自動裝箱與拆箱、周遊循環、變長參數。

條件編譯:編譯器在編譯時隻對滿足條件的代碼進行編譯,而将不滿足條件的代碼舍棄。Java 語言可以使用條件為布爾常量值的 if 語句進行條件編譯。

八、晚期(運作期)優化

1、HotSpot 虛拟機内的即時編譯器

(1)解釋器與編譯器

當程式需要迅速啟動和執行時,解釋器可以首先發揮作用,省去編譯的時間,立即執行。

在程式運作後,随着時間的推移,編譯器把越來越多的代碼編譯成本地代碼後,可以擷取更高的執行效率。

(2)C1、C2 編譯器

C1 編譯器(Client Compiler):運作在 Client 模式。

C2 編譯器(Server Compiler):運作在 Server 模式。

(3)混合模式、解釋模式與編譯模式

混合模式:解釋器與編譯器搭配使用的方式。

解釋模式:全部代碼都使用解釋方式執行,編譯器完全不介入工作。

編譯模式:優先采用編譯方式執行,但是解釋器仍會在編譯無法進行時介入執行過程。

(4)分層編譯

分層編譯根據編譯器編譯、優化的規模與耗時,劃分出不同的編譯層次。

第 0 層:程式解釋執行,解釋器不開啟性能監控功能,可觸發第 1 層編譯。

第 1 層:也稱 C1 編譯,将位元組碼編譯為本地代碼,進行簡單、可靠的優化,必要時加入性能監控的邏輯。

第 2 層(或 2 層以上):也稱 C2 編譯,也是将位元組碼編譯為本地代碼,但會啟用一些編譯耗時較長的優化,甚至會根據性能監控資訊進行一些不可靠的激進優化。

2、即時編譯觸發條件

(1)熱點代碼

被多次調用的方法。

被多次執行的循環體。

(2)熱點探測

判斷一段代碼是不是熱點代碼,是不是需要觸發即時編譯,這樣的行為稱為熱點探測。

基于采樣的熱點探測:虛拟機周期性地檢查各個線程的棧頂,如果發現某個方法經常出現在棧頂,那這個方法就是“熱點代碼”。

基于計數器的熱點探測:虛拟機為每個方法(甚至是代碼塊)建立計數器,統計方法的執行次數,如果執行次數超過一定門檻值就認為它是“熱點代碼”。

HotSpot 虛拟機使用的是基于計數器的熱點探測方法,它為每個方法準備了兩類計數器。

方法調用計數器:統計方法被調用的次數。

回邊計數器:統計一個方法中循環體代碼執行的次數。

3、編譯優化技術

公共子表達式消除:如果一個表達式 E 已經計算過了,并且從先前計算到現在 E 中所有變量的值都沒有變化,那麼 E 的這次出現就成了公共子表達式。對于這種表達式,沒有必要再次進行計算,直接用前面計算過的表達式結果代替 E 即可。

數組邊界檢查消除:編譯器通過資料流分析判定數組下标是否會越界,如果分析後确定不會越界,那麼可以把數組的上下界檢查消除。

方法内聯:把目标方法的代碼“複制”到發起調用的方法之中,避免發生真實的方法調用。

逃逸分析:當一個對象在方法中定義後,如果它被外部方法所引用或被外部線程通路到,那麼就說這個對象發生了逃逸。如果一個對象不會逃逸到方法或線程之外,那麼可以為這個變量進行一些高效的優化,比如棧上配置設定、同步消除、标量替換等。

九、Java 記憶體模型與線程

1、Java 記憶體模型

(1)主記憶體與工作記憶體

所有的變量都存儲在主記憶體中。每條線程有自己的工作記憶體,工作記憶體中儲存了被該線程使用到的變量的主記憶體副本拷貝。

線程對變量的操作必須在工作記憶體中進行,而不能直接讀寫主記憶體中的變量。

不同的線程之間無法直接通路對方工作記憶體中的變量,線程間變量值的傳遞需要通過主記憶體來完成。

(2)記憶體間互動操作

lock(鎖定):把一個主記憶體變量辨別為一條線程獨占的狀态。

unlock(解鎖):把一個處于鎖定狀态的主記憶體變量釋放出來。

read(讀取):把一個變量的值從主記憶體傳輸到線程的工作記憶體中,以便随後的 load 動作使用。

load(載入):把 read 操作從主記憶體中得到的變量值放入工作記憶體的變量副本中。

use(使用):把工作記憶體中一個變量的值傳遞給執行引擎,每當虛拟機遇到一個需要使用到變量的值的位元組碼指令時将會執行這個操作。

assign(指派):把一個從執行引擎接收到的值賦給工作記憶體的變量,每當虛拟機遇到一個給變量指派的位元組碼指令時執行這個操作。

store(存儲):把工作記憶體中一個變量的值傳送到主記憶體中,以便随後的 write 操作使用。

write(寫入):把 store 操作從工作記憶體中得到的變量的值放入主記憶體的變量中。

(3)volatile 的作用

保證變量對所有線程的可見性。

禁止指令重排序優化。

(4)原子性、可見性與有序性

原子性:

基本資料類型的通路讀寫具備原子性: Java 記憶體模型直接保證了 read、load、assign、use、store 和 write 操作的原子性。

synchronized 代碼塊之間的操作具備原子性:底層通過 lock 和 unlock 操作實作。

可見性:當一個線程修改了共享變量的值,其他線程能夠立即得知這個修改。Java 記憶體模型通過在變量修改後将新值同步回主記憶體,在變量讀取前從主記憶體重新整理變量值這種依賴主記憶體作為傳遞媒介的方式來實作可見性。

有序性:如果在本線程内觀察,所有的操作都是有序的;如果在一個線程中觀察另一個線程,所有的操作都是無序的。前半句是指“線程内表現為串行的語義”,後半句是指“指令重排序”現象和“工作記憶體與主記憶體同步延遲”現象。

(5)先行發生原則

程式次序規則:在一個線程内,按照程式代碼順序,書寫在前的操作先行發生于書寫在後的操作。準确地說,是控制流順序而不是程式代碼順序,因為要考慮分支、循環等結構。

管程鎖定規則:一個 unlock 操作先行發生于後面(時間上的先後順序)對同一個鎖的 lock 操作。

volatile 變量規則:對一個 volatile 變量的寫操作先行發生于後面(時間上的先後順序)對這個變量的讀操作。

線程啟動規則:Thread 對象的 start() 方法先行發生于此線程的每一個動作。

線程終止規則:線程中的所有操作都先行發生于對此線程的終止檢測。

線程中斷規則:對線程 interrupt() 方法的調用先行發生于被中斷線程檢測到中斷事件的發生。

對象終結規則:一個對象的初始化完成(構造函數執行結束)先行發生于它的 finalize() 方法的開始。

傳遞性:如果操作 A 先行發生于操作 B,操作 B 先行發生于操作 C,那麼可以得出操作 A 先行發生于操作 C。

2、Java 與線程

(1)線程的實作

使用核心線程實作:核心線程就是直接由作業系統核心支援的線程。

使用使用者線程實作:使用者線程完全建立在使用者空間的線程庫上,系統核心不能感覺線程的存在。

使用使用者線程加輕量級程序混合實作:使用者線程還是完全建立在使用者空間中,而作業系統提供支援的輕量級程序則作為使用者線程和核心線程之間的橋梁。

(2)Java 線程排程

協同式線程排程:線程的執行時間由線程本身來控制,線程執行完之後,主動通知系統切換到另外一個線程上。

搶占式線程排程:每個線程由系統來配置設定執行時間,線程的切換不由線程本身來決定。

Java 使用的線程排程方式就是搶占式排程。

(3)線程狀态

建立(New):線程建立後尚未啟動。

運作(Runable):包括了作業系統線程狀态中的 Running 和 Ready,處于此狀态的線程有可能正在執行,也有可能正在等待着 CPU 為它配置設定執行時間。

無限期等待(Waiting):不會被配置設定 CPU 執行時間,等待着被其他線程顯式地喚醒。

限期等待(Timed Waiting):不會被配置設定 CPU 執行時間,無須等待被其他線程顯式地喚醒,在一定時間之後會由系統自動喚醒。

阻塞(Blocked):線程被阻塞了,在等待着擷取到一個排他鎖。在程式等待進入同步區域的時候,線程将進入這種狀态。

結束(Terminated):已終止線程的線程狀态,線程已經結束執行。

十、線程安全與鎖優化

1、Java 語言中的線程安全

按線程安全的“安全程度”由強至弱排序,可以将多個線程的共享資料分為 5 類:不可變、絕對線程安全、相對線程安全、線程相容和線程對立。

不可變:不可變的對象一定是線程安全的,無論是對象的方法實作還是方法的調用者,都不需要再采取任何的線程安全保障措施。

絕對線程安全:必須滿足“不管運作時環境如何,調用者都不需要任何額外的同步措施”。

相對線程安全:就是我們通常意義上所講的線程安全,它需要保證對一個對象單獨的操作是線程安全的,但是對于一些特定順序的連續調用,則需要在調用端使用額外的同步手段來保證調用的正确性。

線程相容:對象本身并不是線程安全的,但可以通過在調用端正确地使用同步手段來保證對象在并發環境中可以安全地使用。

線程對立:無論調用端是否采取了同步措施,都無法在多線程環境中并發使用的代碼。

2、線程安全的實作方法

互斥同步(阻塞同步):同步是指在多個線程并發通路共享資料時,保證共享資料在同一個時刻隻被一個線程使用,而互斥是實作同步的一種手段。

非阻塞同步:在進行同步操作時,不需要把線程挂起,而是先進行操作,如果沒有其他線程争用共享資料,那操作就成功了;如果共享資料有争用,産生了沖突,那就采取其他的補償措施。

無同步方案:

可重入代碼(純代碼):如果一個方法的傳回結果是可以預測的,隻要輸入了相同的資料,就都能傳回相同的結果,那它就滿足可重入性的要求,當然也就是線程安全的。

線程本地存儲:如果能保證使用共享資料的代碼在同一個線程中執行,那麼就可以把共享資料的可見範圍限制在同一個線程之内。這樣,無須同步也能保證線程之間不出現資料争用的問題。

3、鎖優化

自旋鎖:如果實體機有多個處理器,能讓多個線程同時并行執行,那麼可以讓後面請求鎖的線程“稍等一下”,但不放棄處理器的執行時間,然後看看持有鎖的線程是否很快就會釋放鎖。為了讓線程等待,隻需讓線程執行一個忙循環(自旋),這就是所謂的自旋鎖。

鎖消除:鎖消除是指虛拟機即時編譯器在運作時,對一些代碼上要求同步,但是被檢測到不可能存在共享資料競争的鎖進行消除。

鎖粗化:如果一系列的連續操作都對同一個對象反複加鎖和解鎖,甚至加鎖操作是出現在循環體中的,那麼虛拟機将會把加鎖同步的範圍擴充(粗化)到整個操作序列的外部,這樣隻需要加鎖一次就可以了。

輕量級鎖:輕量級鎖并不是用來代替重量級鎖的,而是在沒有多線程競争的前提下,減少傳統的重量級鎖使用作業系統互斥量産生的性能消耗。對象頭的 Mark Word 有個鎖标志位,用于辨別同步對象的鎖狀态。

偏向鎖:偏向鎖是指這個鎖會偏向于第一個獲得它的線程,如果在接下來的執行過程中,該鎖沒有被其他線程擷取,則持有偏向鎖的線程将永遠不需要再進行同步。

原文位址

https://www.cnblogs.com/jingqueyimu/p/12716260.html