《深刻理解 Java 虛擬機》筆記整理

正文

1、Java 內存區域與內存溢出異常

一、運行時數據區域

  • 程序計數器:當前線程所執行的字節碼的行號指示器。線程私有。
  • Java 虛擬機棧:Java 方法執行的內存模型。線程私有。
  • 本地方法棧:Native 方法執行的內存模型。線程私有。
  • Java 堆:存放對象實例。分爲新生代(Eden 空間、From Survivor 空間、To Survivor 空間)和老年代。線程共享。
  • 方法區:存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。也稱爲「永久代」。線程共享。
  • 運行時常量池:方法區的一部分,用於存放編譯期生成的各類字面量和符號引用。 線程共享。
  • 直接內存

二、對象的建立

類加載檢查 -> 分配內存 -> 初始化零值 -> 設置對象頭 -> 執行 init 方法。html

  • 類加載檢查:檢查 new 指令的參數可否在常量池中定位到一個類的符號引用,以及這個符號引用表明的類是否已被加載、解析和初始化過。
  • 分配內存:把一塊肯定大小的內存從 Java 堆中劃分出來。
  • 初始化零值:將分配到的內存空間初始化爲零值(不包括對象頭)。
  • 設置對象頭:虛擬機須要對對象進行必要的設置,這些信息存放在對象的對象頭中。
  • 執行 init 方法:把對象按照程序員的意願進行初始化。

三、對象的內存佈局

  • 對象頭
    • Mark Word:存儲對象自身的運行時數據。
    • 類型指針:存儲對象的類元數據的指針。
  • 實例數據:對象真正存儲的有效信息,也是在程序代碼中所定義的各類類型的字段內容。
  • 對齊填充:僅僅起着佔位符的做用。

四、對象的訪問定位

  • 句柄:引用中存儲的是對象的句柄地址。Java 堆中劃分出一塊內存做爲句柄池,句柄中包含了對象實例數據、類型數據二者的具體地址信息。
  • 直接指針:引用中存儲的直接就是對象的地址。

五、OutOfMemoryError 異常

  • Java 堆溢出
  • 虛擬機棧和本地方法棧溢出
  • 方法區和運行時常量池溢出
  • 本機直接內存溢出

2、垃圾收集器與內存分配策略

一、判斷對象是否可用

  • 引用計數算法:給對象添加一個引用計數器,每當有一個地方引用它時,計數器值加 1;當引用失效時,計數器值減 1;任什麼時候刻計數器爲 0 的對象就是不可能再被使用的。
  • 可達性分析算法:經過一系列被稱爲「GC Roots」的對象做爲起點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到 GC Roots 沒有任何引用鏈相連時,則此對象不可用。

二、四種引用

  • 強引用:相似「Object obj = new Object()」的引用。只要強引用還存在,對象就永遠不會回收。
  • 軟引用:用來描述一些還有用但並不是必需的對象。內存不足時,對象有可能被回收。
  • 弱引用:用來描述非必需的對象,但強度比軟引用弱。GC時,不管內存是否足夠,對象都會被回收。
  • 虛引用:也稱幽靈引用或幻影引用,虛引用不會對對象的生存時間構成影響。虛引用的惟一做用就是能在對象被回收時收到一個系統通知。

三、垃圾收集算法

  • 標記-清除算法:分爲「標記」和「清除」兩個階段。首先標記出全部須要回收的對象,而後再統一回收全部被標記的對象。會產生大量不連續的內存碎片。
  • 複製算法:將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中一塊。當一塊內存用完時,就將還存活的對象複製到另外一塊,而後再把已使用過的內存空間一次清理掉。
  • 標記-整理算法:首先標記出全部須要回收的對象,而後將全部存活對象向一端移動,最後直接清理掉端邊界之外的內存。
  • 分代收集算法:根據對象存活週期的不一樣,將 Java 堆劃分爲新生代和老年代,而後根據各個年代的特色採用最適當的收集算法。
    • 新生代:採用複製算法。
    • 老年代:採用「標記-清除」或「標記-整理」算法。

四、垃圾收集器

  • Serial 收集器:單線程。新生代收集器。
  • ParNew 收集器:Serial 收集器的多線程版本。新生代收集器。
  • Parallel Scavenge 收集器:多線程。新生代收集器。關注吞吐量。
  • Serial Old 收集器:Serial 收集器的老年代版本。單線程。使用「標記-整理」算法。
  • Parallel Old 收集器:Parallel Scavenge 收集器的老年代版本。多線程。使用「標記-整理」算法。
  • CMS 收集器:併發收集器。使用「標記-清除」算法。關注點是如何縮短垃圾收集時用戶線程的停頓時間。
  • G1 收集器:面向服務端應用。並行與併發、分代收集、空間整合、可預測停頓時間。

五、內存分配與回收策略

  • 對象優先在 Eden 分配。
  • 大對象直接進入老年代。
  • 長期存活的對象進入老年代。
  • 動態對象年齡斷定。
  • 空間分配擔保。

3、虛擬機性能監控與故障處理工具

一、JDK 的命令行工具

  • jps:顯示正在運行的虛擬機進程。經常使用命令:jps -l
  • jstat:監視虛擬機各類運行狀態信息。經常使用命令:jstat -gcutil <pid>
  • jinfo:顯示虛擬機配置信息。經常使用命令:jinfo -flags <pid>
  • jmap:主要用於生成堆轉儲快照。經常使用命令:jmap -dump:format=b,file=<filename> <pid>
  • jhat:分析 jmap 生成的堆轉儲快照。經常使用命令:jhat <filename>
  • jstack:顯示虛擬機當前時刻的線程堆棧信息。經常使用命令:jstack -l <pid>

二、JDK 的可視化工具

  • JConsole:Java 監視與管理控制檯
  • VisualVM:多合一故障處理工具

4、類文件結構

一、無關性的基石

  • 各類不一樣平臺的虛擬機
  • 全部平臺都統一使用的字節碼存儲格式

二、Class 類文件的結構

(1)Class 文件的數據類型

  • 無符號數:基本數據類型,以 u一、u二、u四、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 文件的版本:第 五、6 個字節是次版本號,第 七、8 個字節是主版本號。
  • 常量池:能夠理解爲 Class 文件中的資源倉庫。主要存放字面量和符號引用。每一項常量都是一個表。
  • 訪問標誌:用於識別一些類或接口層次的訪問信息,包括:這個 Class 是類仍是接口、是否認義爲 public、是否認義爲 abstract、是否聲明爲 final(只有類可設置)等。
  • 類索引、父類索引與接口索引集合:Class 文件由這三項數據肯定這個類的繼承關係。
  • 字段表集合:用於描述接口或類中聲明的變量。包括類變量和實例變量,但不包括在方法內部聲明的局部變量。
  • 方法表集合:用於描述接口或類中聲明的方法。
  • 屬性表集合:在 Class 文件、字段表、方法表均可以攜帶本身的屬性表集合,以用於描述某些場景專有的信息。

三、字節碼指令簡介

  • 加載和存儲指令:用於將數據在棧幀中的局部變量表和操做數棧之間來回傳輸。
  • 運算指令:用於對兩個操做數以上的值進行某種特定運算,並把結果從新存入到操做數棧頂。
  • 類型轉換指令:將兩種不一樣的數值類型進行相互轉換。
  • 對象建立與訪問指令。
  • 操做數棧管理指令:用於直接操做操做數棧。
  • 控制轉移指令:讓 Java 虛擬機有條件或無條件地從指定位置的指令繼續執行程序,而不是從控制轉移指令的下一條指令繼續執行程序。可認爲控制轉移指令就是在有條件或無條件地修改 PC 寄存器的值。
  • 方法調用和返回指令。
  • 異常處理指令。
  • 同步指令:支持方法級的同步和方法內部一段指令序列的同步。

5、虛擬機類加載機制

一、類加載的過程

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

  • 加載:獲取二進制字節流,並在內存中生成一個表明這個類的 java.lang.Class 對象,做爲方法區這個類的各類數據的訪問入口。
  • 驗證:確保 Class 文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。
    • 文件格式驗證:驗證字節流是否符合 Class 文件格式的規範,而且能被當前版本的虛擬機處理。
    • 元數據驗證:對字節碼描述的信息進行語義分析,以保證其描述的信息符合 Java 語言規範的要求。
    • 字節碼驗證:經過數據流和控制流分析,肯定程序語義是合法的、符合邏輯的。
    • 符號引用驗證:對符合引用進行匹配性校驗,確保解析動做能正常執行。
  • 準備:爲類變量分配內存並設置初始值。
  • 解析:將常量池內的符號引用替換爲直接引用。
  • 初始化:根據程序員的主觀計劃去初始化類變量和其餘資源。

二、類加載器

  • 啓動類加載器(Bootstrap ClassLoader):負責將存放在 <JAVA_HOME>\lib 目錄的,或者 -Xbootclasspath 參數所指定路徑中的,能被虛擬機識別的類庫加載到虛擬機內存中。
  • 擴展類加載器(Extension ClassLoader):負責加載 <JAVA_HOME>\lib\ext 目錄中的,或者 java.ext.dirs 系統變量所指定路徑中的全部類庫。
  • 應用程序類加載器(Application ClassLoader):負責加載用戶類路徑上所指定的類庫。

三、雙親委派模型

若是一個類加載器收到類加載的請求,它會先把這個請求委派給父加載器去完成,而不會本身去嘗試加載這個類。只有父加載器沒法完成這個加載請求時,子加載器纔會嘗試本身去加載。程序員

6、虛擬機字節碼執行引擎

一、運行時棧幀結構

棧幀(Stack Frame)是用於支持虛擬機進行方法調用和方法執行的數據結構。棧幀存儲了方法的局部變量表、操做數棧、動態鏈接、方法返回地址和一些額外的附加信息。每個方法從調用開始至執行完成的過程,都對應着一個棧幀在虛擬機裏面從入棧到出棧的過程。算法

  • 局部變量表:是一組變量值存儲空間,用於存放方法參數和方法內部定義的局部變量。
  • 操做數棧:也稱爲操做棧,它是一個後入先出的棧。操做數棧的每個元素能夠是任意的 Java 數據類型。
  • 動態鏈接:每一個棧幀都包含一個指向運行時常量池中,該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程當中的動態鏈接。
  • 方法返回地址:方法退出後須要返回到方法被調用的位置,程序才能繼續執行。
  • 附加信息:虛擬機規範容許具體的虛擬機實現增長一些規範裏沒有描述的信息到棧幀中,例如與調試相關的信息。

2、方法調用

方法調用並不等於方法執行,方法調用階段惟一的任務就是肯定被調用方法的版本(即調用哪個方法)。此時,在 Class 文件裏存儲的只是符號引用,而不是直接引用,只有在類加載期間,甚至是運行期間才能肯定目標方法的直接引用。數組

  • 解析:在類加載的解析階段,將方法的符號引用轉化爲直接引用,這類方法調用稱爲解析。這種解析能成立的前提是:方法在程序執行以前有一個可肯定的調用版本,而且這個方法的調用版本在運行期不可改變,即「編譯期可知,運行期不可變」。
  • 分派
    • 靜態分派:在編譯期依賴靜態類型(又稱外觀類型)來定位方法執行版本的分派動做,稱爲靜態分派。靜態分派的典型應用是方法重載。
    • 動態分派:在運行期根據實際類型肯定方法執行版本的分派過程,稱爲動態分派。動態分派的典型應用是方法重寫。

7、早期(編譯期)優化

一、Javac 編譯過程

(1)解析與填充符號表

  • 詞法分析:將源代碼的字符流轉變爲標記(Token)集合,標記是編譯過程的最小元素,關鍵字、變量名、字面量、運算符均可以成爲標記。
  • 語法分析:根據 Token 序列構造抽象語法樹。
  • 填充符號表:符號表是由一組符號地址和符號信息構成的表格,能夠把它想象成哈希表中 K-V 值對的形式。

(2)註解處理

在編譯期間對註解進行處理。能夠讀取、修改、添加抽象語法樹中的任何元素。安全

(3)語義分析與字節碼生成

  • 語義分析:對結構上正確的源程序進行上下文邏輯審查。
    • 標註檢查:包括變量使用前是否已被聲明、變量與賦值之間的數據類型是否可以匹配等。
    • 數據及控制流分析:對程序上下文邏輯進行更進一步的驗證,包括局部變量在使用前是否有賦值、方法的每條路徑是否都有返回值、是否全部的受查異常都被正確處理等。
  • 解語法糖:虛擬機運行時並不支持語法糖的語法,所以,須要在編譯階段還原回簡單的基礎語法結構。
  • 字節碼生成:把前面各個步驟所生成的信息(語法樹、符號表)轉化成字節碼寫到磁盤中,同時還進行了少許的代碼添加和轉換工做。

二、Java 語法糖

  • 泛型與類型擦除:泛型的本質是參數化類型的應用,即將所操做的數據類型指定爲一個參數。
  • 自動裝箱與拆箱、遍歷循環、變長參數。
  • 條件編譯:編譯器在編譯時只對知足條件的代碼進行編譯,而將不知足條件的代碼捨棄。Java 語言可使用條件爲布爾常量值的 if 語句進行條件編譯。

8、晚期(運行期)優化

一、HotSpot 虛擬機內的即時編譯器

(1)解釋器與編譯器

  • 當程序須要迅速啓動和執行時,解釋器能夠首先發揮做用,省去編譯的時間,當即執行。
  • 在程序運行後,隨着時間的推移,編譯器把愈來愈多的代碼編譯成本地代碼後,能夠獲取更高的執行效率。

(2)C一、C2 編譯器

  • C1 編譯器(Client Compiler):運行在 Client 模式。
  • C2 編譯器(Server Compiler):運行在 Server 模式。

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

  • 混合模式:解釋器與編譯器搭配使用的方式。
  • 解釋模式:所有代碼都使用解釋方式執行,編譯器徹底不介入工做。
  • 編譯模式:優先採用編譯方式執行,可是解釋器仍會在編譯沒法進行時介入執行過程。

(4)分層編譯

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

  • 第 0 層:程序解釋執行,解釋器不開啓性能監控功能,可觸發第 1 層編譯。
  • 第 1 層:也稱 C1 編譯,將字節碼編譯爲本地代碼,進行簡單、可靠的優化,必要時加入性能監控的邏輯。
  • 第 2 層(或 2 層以上):也稱 C2 編譯,也是將字節碼編譯爲本地代碼,但會啓用一些編譯耗時較長的優化,甚至會根據性能監控信息進行一些不可靠的激進優化。

二、即時編譯觸發條件

(1)熱點代碼

  • 被屢次調用的方法。
  • 被屢次執行的循環體。

(2)熱點探測

判斷一段代碼是否是熱點代碼,是否是須要觸發即時編譯,這樣的行爲稱爲熱點探測。數據結構

  • 基於採樣的熱點探測:虛擬機週期性地檢查各個線程的棧頂,若是發現某個方法常常出如今棧頂,那這個方法就是「熱點代碼」。
  • 基於計數器的熱點探測:虛擬機爲每一個方法(甚至是代碼塊)創建計數器,統計方法的執行次數,若是執行次數超過必定閾值就認爲它是「熱點代碼」。

HotSpot 虛擬機使用的是基於計數器的熱點探測方法,它爲每一個方法準備了兩類計數器。多線程

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

三、編譯優化技術

  • 公共子表達式消除:若是一個表達式 E 已經計算過了,而且從先前計算到如今 E 中全部變量的值都沒有變化,那麼 E 的此次出現就成了公共子表達式。對於這種表達式,沒有必要再次進行計算,直接用前面計算過的表達式結果代替 E 便可。
  • 數組邊界檢查消除:編譯器經過數據流分析斷定數組下標是否會越界,若是分析後肯定不會越界,那麼能夠把數組的上下界檢查消除。
  • 方法內聯:把目標方法的代碼「複製」到發起調用的方法之中,避免發生真實的方法調用。
  • 逃逸分析:當一個對象在方法中定義後,若是它被外部方法所引用或被外部線程訪問到,那麼就說這個對象發生了逃逸。若是一個對象不會逃逸到方法或線程以外,那麼能夠爲這個變量進行一些高效的優化,好比棧上分配、同步消除、標量替換等。

9、Java 內存模型與線程

一、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。

二、Java 與線程

(1)線程的實現

  • 使用內核線程實現:內核線程就是直接由操做系統內核支持的線程。
  • 使用用戶線程實現:用戶線程徹底創建在用戶空間的線程庫上,系統內核不能感知線程的存在。
  • 使用用戶線程加輕量級進程混合實現:用戶線程仍是徹底創建在用戶空間中,而操做系統提供支持的輕量級進程則做爲用戶線程和內核線程之間的橋樑。

(2)Java 線程調度

  • 協同式線程調度:線程的執行時間由線程自己來控制,線程執行完以後,主動通知系統切換到另一個線程上。
  • 搶佔式線程調度:每一個線程由系統來分配執行時間,線程的切換不禁線程自己來決定。

Java 使用的線程調度方式就是搶佔式調度。併發

(3)線程狀態

  • 新建(New):線程建立後還沒有啓動。
  • 運行(Runable):包括了操做系統線程狀態中的 Running 和 Ready,處於此狀態的線程有可能正在執行,也有可能正在等待着 CPU 爲它分配執行時間。
  • 無限期等待(Waiting):不會被分配 CPU 執行時間,等待着被其餘線程顯式地喚醒。
  • 限期等待(Timed Waiting):不會被分配 CPU 執行時間,無須等待被其餘線程顯式地喚醒,在必定時間以後會由系統自動喚醒。
  • 阻塞(Blocked):線程被阻塞了,在等待着獲取到一個排他鎖。在程序等待進入同步區域的時候,線程將進入這種狀態。
  • 結束(Terminated):已終止線程的線程狀態,線程已經結束執行。

10、線程安全與鎖優化

一、Java 語言中的線程安全

按線程安全的「安全程度」由強至弱排序,能夠將多個線程的共享數據分爲 5 類:不可變、絕對線程安全、相對線程安全、線程兼容和線程對立。

  • 不可變:不可變的對象必定是線程安全的,不管是對象的方法實現仍是方法的調用者,都不須要再採起任何的線程安全保障措施。
  • 絕對線程安全:必須知足「無論運行時環境如何,調用者都不須要任何額外的同步措施」。
  • 相對線程安全:就是咱們一般意義上所講的線程安全,它須要保證對一個對象單獨的操做是線程安全的,可是對於一些特定順序的連續調用,則須要在調用端使用額外的同步手段來保證調用的正確性。
  • 線程兼容:對象自己並非線程安全的,但能夠經過在調用端正確地使用同步手段來保證對象在併發環境中能夠安全地使用。
  • 線程對立:不管調用端是否採起了同步措施,都沒法在多線程環境中併發使用的代碼。

二、線程安全的實現方法

  • 互斥同步(阻塞同步):同步是指在多個線程併發訪問共享數據時,保證共享數據在同一個時刻只被一個線程使用,而互斥是實現同步的一種手段。
  • 非阻塞同步:在進行同步操做時,不須要把線程掛起,而是先進行操做,若是沒有其餘線程爭用共享數據,那操做就成功了;若是共享數據有爭用,產生了衝突,那就採起其餘的補償措施。
  • 無同步方案
    • 可重入代碼(純代碼):若是一個方法的返回結果是能夠預測的,只要輸入了相同的數據,就都能返回相同的結果,那它就知足可重入性的要求,固然也就是線程安全的。
    • 線程本地存儲:若是能保證使用共享數據的代碼在同一個線程中執行,那麼就能夠把共享數據的可見範圍限制在同一個線程以內。這樣,無須同步也能保證線程之間不出現數據爭用的問題。

三、鎖優化

  • 自旋鎖:若是物理機有多個處理器,能讓多個線程同時並行執行,那麼可讓後面請求鎖的線程「稍等一下」,但不放棄處理器的執行時間,而後看看持有鎖的線程是否很快就會釋放鎖。爲了讓線程等待,只需讓線程執行一個忙循環(自旋),這就是所謂的自旋鎖。
  • 鎖消除:鎖消除是指虛擬機即時編譯器在運行時,對一些代碼上要求同步,可是被檢測到不可能存在共享數據競爭的鎖進行消除。
  • 鎖粗化:若是一系列的連續操做都對同一個對象反覆加鎖和解鎖,甚至加鎖操做是出如今循環體中的,那麼虛擬機將會把加鎖同步的範圍擴展(粗化)到整個操做序列的外部,這樣只須要加鎖一次就能夠了。
  • 輕量級鎖:輕量級鎖並非用來代替重量級鎖的,而是在沒有多線程競爭的前提下,減小傳統的重量級鎖使用操做系統互斥量產生的性能消耗。對象頭的 Mark Word 有個鎖標誌位,用於標識同步對象的鎖狀態。
  • 偏向鎖:偏向鎖是指這個鎖會偏向於第一個得到它的線程,若是在接下來的執行過程當中,該鎖沒有被其餘線程獲取,則持有偏向鎖的線程將永遠不須要再進行同步。

相關文章

《深刻理解 Java 虛擬機》讀書筆記:Java 內存區域與內存溢出異常
《深刻理解 Java 虛擬機》讀書筆記:垃圾收集器與內存分配策略
《深刻理解 Java 虛擬機》讀書筆記:虛擬機性能監控與故障處理工具
《深刻理解 Java 虛擬機》讀書筆記:類文件結構
《深刻理解 Java 虛擬機》讀書筆記:虛擬機類加載機制
《深刻理解 Java 虛擬機》讀書筆記:虛擬機字節碼執行引擎
《深刻理解 Java 虛擬機》讀書筆記:早期(編譯期)優化
《深刻理解 Java 虛擬機》讀書筆記:晚期(運行期)優化
《深刻理解 Java 虛擬機》讀書筆記:Java 內存模型與線程
《深刻理解 Java 虛擬機》讀書筆記:線程安全與鎖優化

交流區


微信公衆號:驚卻一目
我的博客:驚卻一目

相關文章
相關標籤/搜索