1、概述
java廣泛應用於嵌入式系統、移動終端、企業伺服器、大型機等各種場合,擺脫了硬體平臺的束縛,實現了「一次編寫,到處運行」的理想
2、java技術體系結構
按照功能來劃分
包括以下幾個組成部分:Java程序設計語言,各種硬體平臺的java虛擬機,Java API類庫,來自商業機構和開源社區的第三方Java類庫,Class文件格式
Java程序設計語言,java虛擬機,Java API類庫統稱為JDK,是用於支持java程序開發的最小環境
Java API類庫中的Java SE API子集和Java虛擬機統稱為JRE,是支持java程序運行的基本環境
按照技術所服務的領域劃分分為4個平臺
Java Card:支持java小程序運行在java小內存設備(如智慧卡)上的平臺
Java ME:支持Java程序運行在行動裝置上的平臺
Java SE:支持面向桌面級應用的平臺
Java EE:支持使用多層架構的企業級應用的平臺
第二部分 自動內存管理機制二、內存區域和內存溢出異常1、運行時數據區程序計數器
Java虛擬機棧
同程序計數器一樣,也是線程私有的。每個方法在執行的時候都會創建一個棧幀,用於存儲局部變量表、操作數棧、動態連結、方法出口等信息。
每一個方法從調用直至執行完成的過程,都對應著一個棧幀在虛擬機棧中入棧和出棧的過程。
局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法需要在棧幀中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表的大小。
如果請求的棧深度超過虛擬機鎖允許的深度,將拋出StackOverFlowError異常。如果拓展無法申請到足夠的內存,將拋出OutOfMemoryError異常。
本地方法棧
Java堆
Java堆是所有線程共享的一塊內存區域,用來存放對象實例,幾乎所有的對象實例都在這裡分配。
Java堆是垃圾回收的主要區域,採用分代收集算法。
Java堆分為新生代和老年代,新生代在細緻一點分為Eden,From Survivor,To Survivor空間。
如果堆中無法完成對象實例的內存分配,且堆也無法擴展時,將拋出OutOfMemoryError異常。
方法區
運行時常量池
是方法區的一部分,Class文件除了有類的版本、欄位、方法、接口等描述信息外,還有一項信息是常量池,用於存放編譯器生成的各種字面量和符號引用,這部分內容將在類加載後進入方法區的運行時常量池存放。
運行時常量池相對於Class文件常量池,具有動態性,運行期間也可以將新的常量放入常量池,比如String類的intern()方法。
當運行時常量池無法申請到更多的內存時,將會拋出OutOfMemoryError異常。
直接內存
2、HotSpot虛擬機對象探秘對象的創建
當遇到new指令時,先判斷這個類是否被加載、解析、初始化過,如果沒有,先執行相應類的加載過程。
類加載檢查通過後,為新生對象分配內存,如果Java堆內存是規整連續的,採用「指針碰撞」的分配方式,如果是不連續規整的,採用「空閒列表」分配方式。內存是否規整取決於垃圾收集器是否帶有壓縮整理功能。
Serial,ParNew等帶有Compact過程的收集器,採用的分配算法是「指針碰撞」。而CMS這種基於Mark-Sweep算法的收集器,通常採用「空閒列表」分配方式。
創建對象涉及到分配內存和指針指向兩個操作,不是原子性的,不是線程安全的。針對這個問題,有兩個解決辦法:1是採用CAS加上失敗重試來保證操作的原子性。2是採用TLAB(Thread Local Allocation Buffer)策略,在Java堆中預先為每一個線程分配一小塊內存,稱為TLAB(Thread Local Allocation Buffer),哪個線程要分配內存就在各自的TLAB上進行內存的分配,只有TLAB用完進行新的TLAB的分配時才需要同步鎖定,虛擬機是否使用TLAB,可以通過 -XX:+/- UseTLAB
內存分配完成後,需要對對象頭進行設置,包括這個對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息。
最後執行init方法,把對象按照程式設計師的意願進行初始化。這樣一個真正可用的對象才算完全生產出來。
對象的內存布局
分為三塊區域,對象頭(Header)、實例數據(Instance Data)、對齊補充(Padding)。
對象頭,存儲對象自身的運行時數據,如哈希碼、對象的GC分代年齡、鎖狀態標誌、偏向線程ID、偏向時間戳,這部分數據的長度在32位和64位虛擬機中分別為32bit和64bit。
另一個部分是類型指針,虛擬機通過這個對象來確定這個對象是哪個類的實例。
對象的訪問定位
3、OutOfMemoryError異常
堆溢出
虛擬機棧和本地方法棧溢出
方法區和運行常量池溢出
本地直接內存溢出
三、垃圾收集器和內存分配策略 1、對象已死嗎?如何確定對象是否還「活著」引用計數器方法
可達性分析方法
再談引用
JDK1.2 之後把引用分為了四種:強引用、軟引用、弱引用、虛引用
強引用:只要強引用還存在,就不會被垃圾回收器回收。類似 Object o=new Object()
軟引用:指一些有用但並非必須的對象,在系統將要發生內存溢出的時候,會將這部分對象回收。SoftReference 類來實現軟引用
弱引用:被弱引用關聯的對象只能生存到下一次垃圾回收。WeakReference 類來實現弱引用
虛引用:一個對象是否有虛引用的存在,完全不會對其生存時間造車影響,也無法通過虛引用取得對象的引用。一個對象設置虛引用的唯一目的是在被垃圾回收的時候收到一個系統通知
對象被回收的過程
當對象進行可達性分析沒有與GC Roots相連的引用鏈,將會被第一次標記,並根據是否需要執行finalize()方法進行一次篩選,對象沒有重寫finalize()或者虛擬機已經調用過finalize(),都被視為不需要執行
如果對象有必要執行finalize,會被放入到F-Queue隊列中,並在稍後由虛擬機自動創建的低優先級的Finalizer線程去觸發它,並不保證等待此方法執行結束。
如果對象在finalize()方法執行中,重新和GC Roots產生了引用鏈,則可以逃脫此次被回收的命運,但finalize()方法只能運行一次,所以並不能通過此方法逃脫下一次被回收
筆者不建議使用這個方法,建議大家完全忘掉這個方法的存在。
回收方法區
2、垃圾回收算法標記-清除
1 是效率問題,標記和清除的效率都不高。
2 是空間問題,會產生大量不連續的內存碎片,碎片太多會都導致大對象無法找到足夠的內存,從提前觸發垃圾回收。
複製算法
標記-整理
分代收集
3、HotSpot算法實現枚舉根節點實現
安全點
安全區域
4、垃圾收集器如果兩個收集器之間有連線,說明可以搭配使用。沒有最好的收集器,也沒有萬能的收集器,只有對應具體應用最合適的收集器。
Serial 收集器
ParNew收集器
新生代收集器,Serial的多線程版本,除了Serial收集器之外,只有它能與CMS收集器配合工作。
-XX:+UseConcMarkSweepGC 選項後默認的新生代收集器,也可以使用 -XX:+UseParNewGC 選項來強制指定它
ParNew收集器在單CPU的環境中,效果不如Serial好,隨著CPU的增加,對於GC時系統資源的利用還是很有效的。
默認開啟的收集線程數和CPU數相等,可以使用 -XX:ParallelGCThreads 指定
Parallel Scavenge收集器
新生代收集器,並行收集器,複製算法,和其他收集器不同,關注點的是吞吐量(垃圾回收時間佔總時間的比例)。提供了兩個參數用於控制吞吐量。
-XX:MaxGCPauseMillis,最大垃圾收集停頓時間,減少GC的停頓時間是以犧牲吞吐量和新生代空間來換取的,不是設置的越小越好
-XX:GCTimeRatio,設置吞吐量大小,值是大於0小於100的範圍,相當於吞吐量的倒數,比如設置成99,吞吐量就為1/(1+99)=1%。
-XX:UseAdaptiveSizePolicy ,這是一個開關參數,打開之後,就不需要設置新生代大小(-Xmn)、Eden和Survival的比例(-XX:SurvivalRatio)、 晉升老年代對象年齡(-XX:PretenureSizeThreshold)等細節參數,收集器會自動調節這些參數。
Serial Old 收集器
Palallel Old 收集器
CMS 收集器
Concurrent Mark Sweep,是一種以獲取最短回收停頓時間為目標的收集器,尤其重視服務的響應速度。基於標記-清除算法實現。
分為四個步驟進行垃圾回收:初始標記,並發標記,重新標記,並發清除。只有初始標記和重新標記需要停頓。
初始標記只是標記一下GC Roots能直接關聯到的對象,速度很快。並發標記就是進行GC Roots的Tracing。
重新標記為了修正並發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間會比初始標記階段稍長,遠比並發時間短。
耗時最長的並發標記和並發清除過程中,處理器可以與用戶線程一起工作。
它並不是完美的,有如下三個比較明顯的缺點:
1、垃圾回收時會佔用一部分線程,導致系統變慢,總吞吐量會降低。
2、無法處理浮動垃圾,需要預留足夠的內存空間給用戶線程使用,可以通過 -XX:CMSInitiatingOccupancyFraction 參數控制觸發垃圾回收的閾值。
如果預留的內存無法滿足程序需要,就會出現「Concurrent Mode Failure」失敗,這時將啟動應急預案,啟用Serial Old 進行垃圾回收,停頓時間會變長
所以-XX:CMSInitiatingOccupancyFraction 參數的值設置的太高,會導致頻繁「Concurrent Mode Failure」失敗,性能反而降低。
3、標記-清理,容易產生內存碎片。-XX:+UseCMSCompactAtFullColletion 開啟碎片整理功能,默認開啟,-XX:CMSFullGCsBeforeCompaction,控制多少次不壓縮的FullGC之後來一次帶壓縮的
G1 收集器
包括新生代和老年代的垃圾回收。和其他收集器相比的優點:並行和並發,分代收集,標記-整理,可預測的停頓。垃圾回收分為以下幾個步驟:
初始標記:標記GC Roots能夠直接關聯到的對象,這階段需要停頓線程,時間很短
並發標記:進行可達性分析,這階段耗時較長,可與用戶程序並發執行
最終標記:修正發生變化的記錄,需要停頓線程,但是可並行執行
篩選回收:對各個Region的回收價值和成本進行排序,根據用戶所期望的停頓時間來執行回收計劃
5、內存分配和回收策略對象優先在Eden分配,當新生區沒有足夠的內存是,通過分配擔保機制提前轉移到老年代中去
大對象直接進入老年代。大對象是指需要大量連續內存空間的對象,虛擬機提供了參數 -XX:PretenureSizeThreshold(只對Serial,PerNew兩個回收器起效),令大於這個值得對象直接在老年代分配,避免了Eden和兩個Survival之間發生大量的內存複製。
長期存活的對象將進入老年代。虛擬機給每個對象定義了對象年齡計數器(Age),如果對象在Eden出生,經過第一次Minor GC後依然存活,並且能被Survival容納的話,將被移動到Survival,對象年齡設為1。對象在Survival中每熬過一次Major GC,年齡就增加1,達到一定程度(默認是15),就會被晉升到老年代。對象晉升老年代的閾值,可以通過參數-XX:MaxTenuringThreShold 指定
動態對象年齡判斷。如果在Survival空間中相同年齡所有對象的大小綜合超過了Survival空間的一半,年齡大於等於這個年齡的對象都會被晉升到老年代。無需等待年齡超過MaxTenuringThreShold指定的年齡
空間分配擔保。只要老年代的連續空間大於新生代對象總和或者歷次晉升的平均大小,就進行Major GC,否則進行Full GC。
四、虛擬機性能監控與故障處理工具1、jps命令用法: jps [options] [hostid]
功能描述: jps是用於查看有權訪問的hotspot虛擬機的進程. 當未指定hostid時,默認查看本機jvm進程
常用參數:-lmvV
詳細說明:JAVA JPS 命令詳解
2、jstat。監視JVM內存工具。語法結構:
Usage: jstat -help|-options
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
例子:jstat -gcutil 25444 1000 5
詳細說明:JDK之jstat的用法
3、jinfo。查看和修改JVM運行參數java -XX:+PrintFlagsFinal -version|grep manageable 【查看JVM中哪些參數可以被jinfo動態修改】
jinfo -flag +PrintGCDetails 105704 【修改參數 PrintGCDetails 的值】
4、jmap。命令用於生成heap dump文件如果不使用這個命令,還可以使用-XX:+HeapDumpOnOutOfMemoryError參數來讓虛擬機出現OOM的時候自動生成dump文件。
jmap不僅能生成dump文件,還可以查詢finalize執行隊列、Java堆和永久代的詳細信息,如當前使用率、當前使用的是哪種收集器等。
詳細使用:JVM調優命令-jmap
5、jstack。Java堆棧跟蹤工具詳細使用:使用jstack精確找到異常代碼,jstack 工具使用,性能調優
注意:dead lock問題,佔用cpu時間最多的線程,頻繁GC
入手點總結:
wait on monitor entry:被阻塞的,肯定有問題,等待synchronized鎖
runnable :注意IO線程,IO阻塞的線程
in Object.wait():注意非線程池等待,調用Object.wait()的對象
五、常見JVM配置說明1、JVM配置1.1、G1
CPU 核數:8;內存(GB):16;磁碟(GB):400
-Xms10g
-Xmx10g
-Xss512k
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=256m
-XX:MaxDirectMemorySize=128M
-XX:+UseG1GC
-XX:MaxGCPauseMillis=150
-XX:+ParallelRefProcEnabled
-XX:+UnlockExperimentalVMOptions
-XX:G1MaxNewSizePercent=70
-XX:ParallelGCThreads=8
-XX:ConcGCThreads=2
1.2、CMS+ParNew
CPU 核數:4;內存(GB):8;磁碟(GB):200
-Xmx4g
-Xms4g
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=256m
-XX:SurvivorRatio=8
-XX:NewRatio=4
-XX:+HeapDumpOnOutOfMemoryError
-XX:+DisableExplicitGC
-XX:+PrintGCDetails
-XX:+UseConcMarkSweepGC
-XX:ParallelGCThreads=4
-XX:+CMSClassUnloadingEnabled
-XX:CMSFullGCsBeforeCompaction=1
-XX:CMSInitiatingOccupancyFraction=72
通用參數
參數
介紹
-Xms10g
-Xmx10g
-Xms10g , 初始化堆大小。通常情況和-Xmx大小設置一樣,避免虛擬機頻繁自動計算後調整堆大小。
-Xmx10g ,最大堆大小。
-XX:MaxDirectMemorySize=128M
本地直接內存大小。適合頻繁的IO操作,例如網絡並發場景(NIO)
直接內存和堆內存比較:
-XX:+PrintGCApplicationStoppedTime
列印垃圾回收期間程序暫停的時間
-XX:+PrintGC
列印GC基本日誌
-XX:+PrintGCDetails
列印GC詳細日誌
-XX:+DisableExplicitGC
關閉System.gc()
-Xss512k
每個線程堆棧大小。每一次方法的調用都對應一個入棧和出棧
-XX:PermSize=256m
-XX:MaxPermSize=256m
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=256m
JDK8以前:
JDK8及以後:
-XX:+HeapDumpOnOutOfMemoryError
JVM異常自動生成堆轉儲
CMS
參數
說明
-XX:+UserConcMarkSweepGC
年老代指定CMS垃圾回收器,新生代默認用ParNew收集
-XX:ParallelGCThreads=4
設置垃圾收集線程數
-XX:CMSInitiatingOccupancyFraction=72
老年代垃圾佔比達到這個閾值開始CMS收集,設置過高容易導致並發收集失敗,會出現SerialOld收集的情況
-XX:+UseCMSCompactAtFullCollection(默認開啟,不需要設置)
在FULL GC的時候, 對年老代開啟碎片整理功能,默認開啟
-XX:CMSFullGCsBeforeCompaction=1
控制FullGC壓縮的間隔。多少次不壓縮的FullGC之後來一次帶壓縮的
-XX:+CMSClassUnloadingEnabled
CMS收集器默認不會對永久代進行垃圾回收。如果希望對永久代進行垃圾回收,可以設置標誌
-XX:+CMSParallelRemarkEnabled
為了減少第二次暫停的時間,開啟並行remark,降低標記停頓
-XX:NewRatio=4
新生代:老年代=1:4
-XX:SurvivorRatio=8
2個survivor和eden的比值。表示2:8
默認為8,也就是說Eden佔新生代的8/10,From倖存區和To倖存區各佔新生代的1/10
G1
參數
說明
-XX:+UseG1GC
使用G1垃圾回收
-XX:MaxGCPauseMillis=150
GC最大暫停時間
-XX:+ParallelRefProcEnabled
打開並行引用處理
-XX:+UnlockExperimentalVMOptions
有些時候當設置一個特定的JVM參數時,JVM會在輸出「Unrecognized VM option」後終止。如果參數輸入是正確的,並且JVM並不識別,需要設置-XX:+UnlockExperimentalVMOptions 來解鎖參數。
-XX:G1NewSizePercent
新生代最小值比例。默認5%
-XX:G1MaxNewSizePercent=70
新生代最大值比例
-XX:ParallelGCThreads=8
STW期間,並行GC線程數
-XX:ConcGCThreads=2
並發標記階段,並行執行的線程數
G1最佳實踐在使用G1垃圾收集器的時候遵循以下實踐可以少走不少彎路:
1、不斷調優暫停時間指標通過XX:MaxGCPauseMillis=x可以設置啟動應用程式暫停的時間,G1在運行的時候會根據這個參數選擇CSet來滿足響應時間的設置。一般情況下這個值設置到100ms或者200ms都是可以的(不同情況下會不一樣),但如果設置成50ms就不太合理。暫停時間設置的太短,就會導致出現G1跟不上垃圾產生的速度。最終退化成Full GC。所以對這個參數的調優是一個持續的過程,逐步調整到最佳狀態。
2、不要設置新生代和老年代的大小G1收集器在運行的時候會調整新生代和老年代的大小。通過改變代的大小來調整對象晉升的速度以及晉升年齡,從而達到我們為收集器設置的暫停時間目標。設置了新生代大小相當於放棄了G1為我們做的自動調優。我們需要做的只是設置整個堆內存的大小,剩下的交給G1自己去分配各個代的大小。
3、關注Evacuation FailureEvacuation Failure類似於CMS裡面的晉升失敗,堆空間的垃圾太多導致無法完成Region之間的拷貝,於是不得不退化成Full GC來做一次全局範圍內的垃圾收集。
六、JVM調優案例分析與實踐
1、Minor GC、Major GC和Full GC之間的區別每次 Minor GC 會清理年輕代的內存
Major GC 是清理老年代,Full GC 是清理整個堆空間—包括年輕代和老年代。大部分時候Major GC和Full GC區分的不是很明顯
不僅僅Major GC和Full GC會stop-the-world。所有的 Minor GC 都會觸發「全世界的暫停(stop-the-world)」,停止應用程式的線程。對於大部分應用程式,停頓導致的延遲都是可以忽略不計的。其中的真相就 是,大部分 Eden 區中的對象都能被認為是垃圾,永遠也不會被複製到 Survivor 區或者老年代空間。如果正好相反,Eden 區大部分新生對象不符合 GC 條件,Minor GC 執行時暫停的時間將會長很多。
2、常用命令查看java進程號
兩種方式都可以查看tomcat進程號
ps -ef | grep java
jps -lmvV |grep java
結果如下:2556
查看進程內線程情況
top -Hp 2556(2556為上一步查詢出來的進程號)
找到佔用cpu時間最長的線程號:3345
得到線程號的十六進位數
printf "%x\n" 3345(輸出為a05)
使用jstack定位問題
jstack 2556 | grep a05
查看內存和swap使用情況。參考:https://www.cnblogs.com/coldplayerest/archive/2010/02/20/1669949.html
free -h
3、問題排查內存和SWAP問題
七、虛擬機類加載機制1、虛擬機把表示類的class文件加載到內存,經過校驗、轉換解析、初始化,最終形成可以被虛擬機直接使用的java類型,這就是虛擬機的類加載機制2、類加載的時機使用new關鍵字實例化對象的時候、讀取一個類的靜態欄位的時候、調用類的靜態方法的時候
使用java.lang.reflect包的方式對類進行反射調用的時候
初始化類,發現其父類還未初始化,需要對父類進行初始化
3、類加載的過程加載。通過類的全限定名獲取到定義此類的二進位字節流。將字節流所代表的靜態存儲結構轉化成方法區的運行時數據結構。在方法區生成這個類的java.lang.Class對象。加載階段和連接階段的部分內容是交叉進行的。用戶可以通過自己寫的類加載器去控制字節流的獲取方式(重寫類加載器的loadClass()方法),
驗證。是連接階段的第一步。目的是確保class文件中的二進位字節流符合虛擬機的要求,不會危及虛擬機自身安全。包括文件格式驗證、元數據驗證、字節碼驗證
準備。是連接階段的第二步。是正式為類變量分配內存空間和設置初始值的階段。這個初始值和初始化階段的賦值不同,這裡指的是變量的默認初始值。另外,如果時final修飾的變量,那麼會在準備階段賦予代碼裡指定的初始值
解析。是連接階段的第三步。是虛擬機將符號引用替換為直接引用的過程
初始化。根據程序代碼去初始化類變量和其他資源
4、類加載器第五部分 高效並發十二、Java內存模型與線程 1、硬體的效率與一致性完成計算任務,處理器必須和內存交互才能完成,比如讀取運算數據,寫入計算結果等。這個I/O操作是很難消除的。計算的處理器和存儲設備的運算速度有幾個數量級的差距。所以現代計算機加入了一層讀寫速度儘可能接近處理器的高速緩存
高速緩存解決了處理器和內存的速度矛盾,卻引入了新的問題:內存一致性。多處理器系統中,各個處理器都有自己的高速緩存,又同時共用內存。為了解決這一問題,在讀寫內存時需要遵循緩存一致性協議。
處理器會對輸入的代碼進行亂序執行優化,類似的,Java虛擬機也存在著指令重排序優化。
2、Java內存模型Java內存模型規定,所有的變量(這個變量和java編程中的變量有區別,它包括了實例欄位、靜態欄位。不包括局部變量和方法參數,因為後者是線程私有的)都存儲在主內存,每條線程有自己的工作內存,工作內存中保存了該線程使用到的變量的拷貝副本,線程對變量的所有操作都必須在工作內存中進行,線程間變量值得傳遞需通過主內存來完成
主內存和工作內存間交互協議,8種原子操作:
volatile是java虛擬機提供的輕量級的同步機制,對於volatile變量的特殊規則:
保證了變量對所有線程的可見性,當一個線程修改了這個變量的值,修改後的值對其他線程來說是立即可見的。普通變量,需要通過把新值會寫到主內存,其他線程從主內存讀取之後才可以看到最新值
禁止指令重排序優化。
無法保證符合操作的原子性,比如i++
通過內存屏障實現的可見性和禁止重排序。不同硬體實現內存屏障的方式不同,Java內存模型屏蔽了這些差異,由JVM來為不同的平臺生成相應的機器碼來完成。X86 處理器只會對寫-讀進行指令重排序,寫volatile變量時,會加lock總線鎖,將cpu緩存寫入主存,其他cpu的讀都會被阻塞,然後其他核的緩存某些對應數據會被標記為失效,那麼其他核下次讀的時候先讀緩存發現失效了,然後去主存讀
關於long和double類型變量的特殊規則:允許虛擬機將沒有被volatile變量修飾的64位數據的讀寫操作劃分為兩次32位的操作來進行。這點就是long和double的非原子性協定
3、Java與線程Java虛擬機實現線程,有三種方式:
(1)通過內核線程實現。jvm中的一個線程對應一個輕量級進程,一個輕量級進程對應一個內核線程。CPU通過調度器對線程進行調度。缺點:
(2)使用用戶線程實現。不需要切換回內核態,也可以支持規模更大的線程數量。部分高性能資料庫的多線程就是使用用戶線程實現的。缺點是沒有系統內核的支援,所有問題需要自己考慮,程序實現比較複雜
(3)內核線程和用戶線程結合
(4)JVM,對於Sun JDK來說,在Windows和LInux系統下,都是使用的一對一的線程模型實現的。
Java線程調度
Java線程狀態轉換
十三、線程安全與鎖優化高效並發是從jdk1.5 到jdk1.6的一個重要改進,HotSpot虛擬機開發團隊耗費了大量的精力去實現鎖優化技術
自旋鎖與自適應自旋。同步互斥對性能最大的影響就是線程掛起、恢復需要從用戶態切換到內核態,切換的過程會造成系統消耗。往往鎖定的代碼段執行時間非常短,為了這個短的時間去掛起和恢復是不值得的。所以提出了自旋鎖的概念,當線程申請獲取一個其他線程佔用的鎖時,這個線程不會立即掛起,而是通過一定次數的循環自旋,這個過程不會釋放cpu的控制權,自適應自旋就是根據上一次自旋的結果來決定這一次自旋的次數
鎖消除。虛擬機即時編譯器在運行時會把檢測到不可能發生共享數據競爭的鎖消除
鎖粗化。一系列的操作都是對同一個對象的加鎖和解鎖,虛擬機檢測到這種情況會將鎖的範圍擴大(粗化)
輕量級鎖
偏向鎖。如果程序中大多數的鎖總是被多個線程訪問,那偏向鎖模式就是多餘的。可以使用參數 -XX:-UseBiasedLocking來禁止偏向鎖
作者:leon66666
出處:http://www.cnblogs.com/wangzhongqiu/
【整理了java程式設計師必須讀的電子書籍,掃碼領取】
備註【1】