本文主要整理自煉術成金JVM教學資料和《深刻理解Java虛擬機》,部分資料整理自網絡,已不明來源java
example程序員
-6
原碼: 10000110
反碼: 11111001
補碼: 11111010
複製代碼
不使用補碼,將0看爲算法
正數:0000 0000
負數:1000 0000
複製代碼
則不一致
使用補碼:數組
負數:1000 0000
反碼:1111 111
補碼:0000 0000 = 正數
複製代碼
正數和負數使用補碼作運算至關於用加法作運算
計算時都是使用補碼進行計算
緩存
當指數位tomcat
計算方式 S*M*2^(e-127)
eg: -5的單精度表示
1 10000001 01000000000000000000000
其符號位 S爲1,表示負數 -1
指數位E:10000001 ,e =129
尾數附加位:指數位不全爲0,則爲1
尾數M: 1+2^-2
;(-2,尾數位由右往左數第二位)
結果:-1 * ( 1+2^-2) * 2^( 129 - 127) = -5
安全
方法區物理上存在於堆裏,並且是在堆的持久代裏面;但在邏輯上,方法區和堆是獨立的 方法區method area
只是JVM規範中定義的一個概念,用於存儲類信息、常量池、靜態變量、JIT編譯後的代碼等數據,具體放在哪裏,不一樣的實現能夠放在不一樣的地方。而永久代是Hotspot虛擬機特有的概念,是方法區的一種實現,別的JVM都沒有這個東西bash
java 8和java 7的某版本後,perm gen 被去除了,取而代之的是metaspace。服務器
不一樣點在於:perm gen 含class metadata、class static variables和interned string
metaspace只含class metadata了,class static variables和interned string被移到java heap上去了(因此java heap使用確定要大一點)網絡
JVM主要管理兩種類型的內存:堆和非堆. 簡單來講堆就是Java代碼可及的內存,是留給開發人員使用的;非堆就是JVM留給本身用的 因此方法區,JVM內部處理或優化所需的內存(如JIT編譯後的代碼緩存),每一個類結構(如運行時常數池,字段和方法數據)以及方法和構造方法的代碼都在非堆內存中.
線程私有
棧由一系列幀組成(故也叫幀棧)
幀保存每一個方法的局部變量表,操做數棧,常量池指針,程序計數器
每一次方法調用建立一個幀,並壓棧
幀中有局部變量表
操做數棧
Java沒有寄存器,全部參數傳遞使用操做數棧
小對象(幾十bytes),在沒有逃逸的狀況下,能夠直接分配在棧上
直接分配在棧上,能夠自動回收,減輕GC壓力
大對象或逃逸對象沒法在棧上分配
逃逸對象:棧內對象被外部對象引用,其做用範圍脫離了當前方法棧
public class AppMain {
//運行時, jvm 把appmain的信息都放入方法區
public static void main(String[] args) {
//main 方法自己放入方法區。
Sample test1 = new Sample( " 測試1 " );
//test1是引用,因此放到棧區裏, Sample是自定義對象應該放到堆裏面
Sample test2 = new Sample( " 測試2 " );
test1.printName();
test2.printName();
}
}
public class Sample {
//運行時, jvm 把appmain的信息都放入方法區
private name;
//new Sample實例後, name 引用放入棧區裏, name 對象放入堆裏
public Sample(String name) {
this .name = name;
}
//print方法自己放入 方法區裏
public void printName() {
System.out.println(name);
}
}
複製代碼
每個線程有一個工做內存和主存獨立 工做內存存放主存中變量的值和拷貝
對於普通變量,一個線程中更新的值,不能立刻反應在其餘變量中 若是須要在其餘線程中當即可見,須要使用 volatile 關鍵字class OrderExample {
int a = 0;
boolean flag = false;
public void writer() {
a = 1;
flag = true;
}
public void reader() {
if (flag) {
int i = a +1;
……
}
}
}
複製代碼
線程A首先執行writer()
方法 線程B線程接着執行reader()
方法 線程B在int i=a+1
是不必定能看到a已經被賦值爲1
Thread.join()
,最後才終結interrupt()
先於被中斷線程的代碼,中斷當即中止finalize()
方法-XX:+TraceClassLoading
:監控類的加載-XX:+PrintClassHistogram
: 按下Ctrl+Break後,打印類的信息XX:+HeapDumpOnOutOfMemoryError
:OOM時導出堆到文件-XX:OnOutOfMemoryError
: 在OOM時,執行一個腳本public class CanReliveObj {
public static CanReliveObj obj;
public static void main(String[] args) throws InterruptedException{
obj=new CanReliveObj();
obj=null; //可復活
System.gc();
Thread.sleep(1000);
if(obj==null){
System.out.println("obj 是 null");
}else{
System.out.println("obj 可用");
}
System.out.println("第二次gc");
obj=null; //不可復活
System.gc();
Thread.sleep(1000);
if(obj==null){
System.out.println("obj 是 null");
}else{
System.out.println("obj 可用");
}
}
@Override
//重寫析構方法
protected void finalize() throws Throwable {
super.finalize();
System.out.println("CanReliveObj finalize called");
obj=this;
}
@Override
public String toString(){
return "I am CanReliveObj";
}
}
複製代碼
對於用可達性分析法搜索不到的對象,GC並不必定會回收該對象。要徹底回收一個對象,至少須要通過兩次標記的過程。
第一次標記:對於一個沒有其餘引用的對象,篩選該對象是否有必要執行finalize()方法,若是沒有執行必要,則意味可直接回收。(篩選依據:是否複寫或執行過finalize()方法;由於finalize方法只能被執行一次)。
第二次標記:若是被篩選斷定位有必要執行,則會放入FQueue隊列,並自動建立一個低優先級的finalize線程來執行釋放操做。若是在一個對象釋放前被其餘對象引用,則該對象會被移除FQueue隊列
Java中一種全局暫停的現象
全局停頓,全部Java代碼中止,native代碼能夠執行,但不能和JVM交互
多半因爲GC引發,也能夠是Dump線程、死鎖檢查、堆Dump
適用於對吞吐量有較高要求, 多CPU、對應用響應時間無要求的中、大型應用。
舉例:後臺處理、科學計算 吞吐量:=運行用戶代碼時間/(運行用戶代碼時間+GC時間)
-XX:+UseParNewGC
-XX:ParallelGCThreads
限制線程數量
0.834: [GC 0.834: [ParNew: 13184K->1600K(14784K), 0.0092203 secs] 13184K->1921K(63936K), 0.0093401 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
複製代碼
-XX:+UseAdaptiveSizePolicy
自適應調節策略是Parallel與ParNew的重要區別-XX:+UseParallelGC
-XX:+UseParallelOldGC
老年代不同而已
1.500: [Full GC [PSYounhttps://user-gold-cdn.xitu.io/2017/12/3/1601bd5a57d6924fen: 2682K->0K(19136K)] [ParOldGen: 28035K->30437K(43712K)] 30717K->30437K(62848K) [PSPermGen: 10943K->10928K(32768K)], 0.2902791 secs] [Times: user=1.44 sys=0.03, real=0.30 secs]
複製代碼
-XX:MaxGCPauseMills
-XX:GCTimeRatio
適用於對響應時間有高要求,多CPU、對應用響應時間有較高要求的中、大型應用。
舉例:Web服務器/應用服務器、電信交換、集成開發環境
特性
Concurrent Mark Sweep 併發標記清除(與用戶線程一塊兒執行 )
標記-清除算法(不是標記壓縮)
併發階段會下降吞吐量(?)
只是針對老年代收集器(新生代使用ParNew/或串行)
-XX:+UseConcMarkSweepGC
運行過程
優:
儘量下降停頓,在併發標記過程當中並不須要全局停頓
劣:
-XX:CMSInitiatingOccupancyFraction
設置觸發GC的閾值-XX:+ UseCMSCompactAtFullCollection
Full GC後,進行一次整理
-XX:+CMSFullGCsBeforeCompaction
-XX:ParallelCMSThreads
參數名稱 | 含義 | 備註 |
---|---|---|
-Xms | 初始堆大小 | 默認空餘堆內存小於40%時,JVM就會增大堆直到-Xmx的最大限制 |
-Xmx | 最大堆大小 | 默認(MaxHeapFreeRatio參數能夠調整)空餘堆內存大於70%時,JVM會減小堆直到 -Xms的最小限制 |
-Xmn | 年輕代大小 | eden+ 2 survivor space,增大年輕代後,將會減少年老代大小,Sun官方推薦配置爲整個堆的3/8 |
-XX:PermSize | 設置持久代(perm gen)初始值 | 持久代是方法區的一種實現 |
-XX:MaxPermSize | 設置持久代最大值 | |
-Xss | 每一個線程的棧大小 | JDK5.0之後每一個線程堆棧大小爲1M,棧越大,線程越少,棧深度越深 |
-XX:NewRatio | 年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代) | -XX:NewRatio=4表示年輕代與年老代所佔比值爲1:4,年輕代佔整個堆棧的1/5,Xms=Xmx而且設置了Xmn的狀況下,該參數不須要進行設置。 |
-XX:SurvivorRatio | Eden區與Survivor區的大小比值 | 設置爲8,則兩個Survivor區與一個Eden區的比值爲2:8,一個Survivor區佔整個年輕代的1/10 |
-XX:MaxTenuringThreshold | 垃圾最大年齡 | 該參數只有在串行GC時纔有效 |
-XX:PretenureSizeThreshold | 對象超過多大是直接在舊生代分配 | 單位字節 新生代採用Parallel Scavenge GC時無效, 另外一種直接在舊生代分配的狀況是大的數組對象,且數組中無外部引用對象. |
參數名稱 | 含義 | 備註 |
---|---|---|
-XX:+UseParallelGC | 新生代使用Parallel收集器+ 老年代串行 | |
-XX:+UseParNewGC | 在新生代使用並行收集器 | |
-XX:ParallelGCThreads | 並行收集器的線程數 | 此值最好配置與處理器數目相等 也適用於CMS |
-XX:+UseParallelOldGC | 新生代使用Parallel收集器+ 老年代並行 | |
-XX:MaxGCPauseMillis | 每次年輕代垃圾回收的最長時間(最大暫停時間) | 若是沒法知足此時間,JVM會自動調全年輕代大小,以知足此值 |
-XX:+UseAdaptiveSizePolicy | 自動選擇年輕代區大小和相應的Survivor區比例 | 設置此選項後,並行收集器會自動選擇年輕代區大小和相應的Survivor區比例,以達到目標系統規定的最低相應時間或者收集頻率等,此值建議使用並行收集器時,一直打開. |
參數名稱 | 含義 | 備註 |
---|---|---|
-XX:+UseConcMarkSweepGC | 使用CMS內存收集 | 新生代使用並行收集器ParNew,老年代使用CMS+串行收集器 |
-XX:CMSFullGCsBeforeCompaction | 多少次後進行內存壓縮 | 因爲併發收集器不對內存空間進行壓縮,整理,因此運行一段時間之後會產生"碎片",使得運行效率下降.此值設置運行多少次GC之後對內存空間進行壓縮,整理 |
-XX+UseCMSCompactAtFullCollection | 在FULL GC的時候, 對年老代的壓縮 | CMS是不會移動內存的, 所以, 這個很是容易產生碎片, 致使內存不夠用, 所以, 內存的壓縮這個時候就會被啓用。 增長這個參數是個好習慣。可能會影響性能,可是能夠消除碎片 |
-XX:CMSInitiatingPermOccupancyFraction | 當永久區佔用率達到這一百分比時,啓動CMS回收 |
參數名稱 | 含義 |
---|---|
-XX:+PrintGC | |
-XX:+PrintGCDetails | |
-XX:+PrintGCTimeStamps | |
-XX:+PrintGCApplicationStoppedTime | 打印垃圾回收期間程序暫停的時間.可與上面混合使用 |
-XX:+PrintHeapAtGC | 打印GC先後的詳細堆棧信息 |
--Xloggc:filename | 把相關日誌信息記錄到文件以便分析 |
-XX:+HeapDumpOnOutOfMemoryError | |
-XX:HeapDumpPath | |
-XX:+PrintCommandLineFlags | 打印出已經被設置過的詳細的 XX 參數的名稱和值 |
項目 | 響應時間優先 | 吞吐量優先 |
---|---|---|
年輕代 | -Xmn儘可能大,直到接近系統的最低響應時間限制-XX:MaxGCPauseMillis,減小年輕代GC,減小到達老年代對象 | -Xmn儘可能大 |
年輕代垃圾回收器 | 併發收集器 | 並行收集器 |
年老代 | 若是堆設置小了,能夠會形成內存碎 片,高回收頻率以及應用暫停而使用傳統的標記清除方式;若是堆大了,則須要較長的收集時間 | |
要參照年輕代和年老代垃圾回收時間與次數 | -XX:NewRatio 年老代設置小一些,這樣能夠儘量回收掉大部分短時間對象,減小中期的對象,而年老代盡存放長期存活對象 | |
年老代垃圾回收器 | 年老代使用併發收集器 | 由於對響應時間沒有要求,垃圾收集能夠並行進行,也能夠串行 |
典型配置
-Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
複製代碼
年老代並行
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
複製代碼
設置每次年輕代垃圾回收的最長時間,若是沒法知足此時間,JVM會自動調全年輕代大小,以知足此值
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100
複製代碼
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
複製代碼
-XX:+UseConcMarkSweepGC
:設置年老代爲併發收集 -XX:+UseParNewGC
:設置年輕代爲並行收集
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5
-XX:+UseCMSCompactAtFullCollection
複製代碼
-XX:CMSFullGCsBeforeCompaction
:因爲併發收集器不對內存空間進行壓縮、整理,因此運行一段時間之後會產生「碎片」,使得運行效率下降。此值設置運行多少次GC之後對內存空間進行壓縮、整理。
-XX:+UseCMSCompactAtFullCollection
:打開對年老代的壓縮。可能會影響性能,可是能夠消除碎片
5.617: [GC 5.617: [ParNew: 43296K->7006K(47808K), 0.0136826 secs] 44992K->8702K(252608K), 0.0137904 secs]
[Times: user=0.03 sys=0.00, real=0.02 secs]
複製代碼
解釋
5.617(時間戳): [GC(Young GC) 5.617(時間戳):
[ParNew(使用ParNew做爲年輕代的垃圾回收器): 43296K(年輕代垃圾回收前的大小)->7006K(年輕代垃圾回收之後的大小)(47808K)(年輕代的總大小), 0.0136826 secs(回收時間)]
44992K(堆區垃圾回收前的大小)->8702K(堆區垃圾回收後的大小)(252608K)(堆區總大小), 0.0137904 secs(回收時間)]
[Times: user=0.03(Young GC用戶耗時) sys=0.00(Young GC系統耗時), real=0.02 secs(Young GC實際耗時)]
複製代碼
[GC [DefNew: 3468K->150K(9216K), 0.0028638 secs][Tenured:
1562K->1712K(10240K), 0.0084220 secs] 3468K->1712K(19456K),
[Perm : 377K->377K(12288K)],
0.0113816 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
複製代碼
Tenured:持久代/老年代
串行收集器:
DefNew:使用-XX:+UseSerialGC
(新生代,老年代都使用串行回收收集器)。
並行收集器:
ParNew:是使用-XX:+UseParNewGC
(新生代使用並行收集器,老年代使用串行回收收集器)或者-XX:+UseConcMarkSweepGC
(新生代使用並行收集器,老年代使用CMS)。
PSYoungGen:是使用-XX:+UseParallelOldGC
(新生代,老年代都使用並行回收收集器)或者-XX:+UseParallelGC
(新生代使用並行回收收集器,老年代使用串行收集器)
garbage-first heap:是使用-XX:+UseG1GC
(G1收集器)
觸發條件就是某GC算法對應區域滿了,或是預測快滿了(好比該區使用比例達到必定比例-對並行/併發,或不夠晉升)
針對HotSpot VM的實現,它裏面的GC其實準確分類只有兩大種:
Major GC一般是跟full GC是等價的,收集整個GC堆。最簡單的分代式GC策略,按HotSpot VM的serial GC的實現來看,觸發條件是:
觸發條件複雜一些,不過大體的原理與串行GC同樣。
例外: Parallel Scavenge(-XX:+UseParallelGC
新生代使用Parallel收集器)框架下,默認是在要觸發full GC前先執行一次young GC,而且兩次GC之間能讓應用程序稍微運行一下,以期下降full GC的暫停時間(由於young GC會盡可能清理了young gen的死對象,減小了full GC的工做量)。控制這個行爲的VM參數是-XX:+ScavengeBeforeFullGC
併發GC的觸發條件就不太同樣。以CMS GC爲例,主要是定時去檢查old gen的使用量,當使用量超過了觸發比例就會啓動一次CMS GC,對old gen作併發收集
-XX:CMSInitiatingOccupancyFraction=80 // old達到80%收集
複製代碼
或者GC過程當中,因爲預留的內存沒法知足程序須要, 出現concurrent mode failure,臨時使用serial old進行Full GC
G1 GC的initial marking(初始標記)的觸發條件是Heap使用比率超過某值,收集時是按照回收價值的優先級,不按照young old區
G1 GC:Young GC + mixed GC(新生代,再加上部分老生代)+ Full GC for G1 GC算法(應對G1 GC算法某些時候的不趕趟,開銷很大);
轉爲方法區數據結構 在Java堆中生成對應的java.lang.Class對象
tomcat和OSGi有作更改
example:類從上往下加載
在工程目錄中添加A.java,自動編譯生成A.class
又指定根加載目錄path,-Xbootclasspath/a:path,從新放一個同名A.class
此時會加載指定根加載目錄下的class文件
注意:以上是jdk默認的類加載模式,但tomcat和OSGi有本身的加載方式
Tomcat:Tomcat的WebappClassLoader 就會先加載本身的Class,找不到再委託parent
OSGi的ClassLoader造成網狀結構,根據須要自由加載Class
直接在控制檯輸入命令,參數具體使用可以使用-help 命令
通常是第一步,方便後續其餘命令調用
列出java進程,相似於ps命令
參數-q能夠指定jps只輸出進程ID ,不輸出類的短名稱
參數-m能夠用於輸出傳遞給Java進程(主函數)的參數
參數-l能夠用於輸出主函數的完整路徑
參數-v能夠顯示傳遞給JVM的參數
查看進程參數 能夠用來查看正在運行的Java應用程序的擴展參數,甚至支持在運行時,修改部分參數
-flag 進程ID:打印指定JVM的參數值
-flag [+|-] 進程ID:設置指定JVM參數的布爾值
-flag = 進程ID:設置指定JVM參數的值
生成Java應用程序的堆快照和對象的統計信息
num #instances #bytes class name
----------------------------------------------
1: 370469 32727816 [C
2: 223476 26486384 <constMethodKlass>
3: 260199 20815920 java.lang.reflect.Method
…..
8067: 1 8 sun.reflect.GeneratedMethodAccessor35
Total 4431459 255496024
複製代碼
打印線程dump
-l
打印鎖信息
-m
打印java和native的幀信息
-F
強制dump,當jstack沒有響應時使用
Jdk1.6版本只有 –l選項
圖形化監控工具
能夠查看Java應用程序的運行概況,監控堆信息、永久區使用狀況、類加載狀況等
Visual VM是一個功能強大的多合一故障診斷和性能監控的可視化工具
堆+線程棧 +直接內存<= 操做系統可分配空間
public static void main(String args[]){
ArrayList<byte[]> list=new ArrayList<byte[]>();
for(int i=0;i<1024;i++){
list.add(new byte[1024*1024]);
}
}
複製代碼
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at geym.jvm.ch8.oom.SimpleHeapOOM.main(SimpleHeapOOM.java:14)
複製代碼
解決方法:增大堆空間,及時釋放內存,分批處理
//生成大量的類
public static void main(String[] args) {
for(int i=0;i<100000;i++){
CglibBean bean = new CglibBean("geym.jvm.ch3.perm.bean"+i,new HashMap());
}
}
複製代碼
Caused by: java.lang.OutOfMemoryError: 【PermGen space】
[Full GC[Tenured: 2523K->2523K(10944K), 0.0125610 secs] 2523K->2523K(15936K),
[Perm : 【4095K->4095K(4096K)】], 0.0125868 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Heap
def new generation total 4992K, used 89K [0x28280000, 0x287e0000, 0x2d7d0000)
eden space 4480K, 2% used [0x28280000, 0x282966d0, 0x286e0000)
from space 512K, 0% used [0x286e0000, 0x286e0000, 0x28760000)
to space 512K, 0% used [0x28760000, 0x28760000, 0x287e0000)
tenured generation total 10944K, used 2523K [0x2d7d0000, 0x2e280000, 0x38280000)
the space 10944K, 23% used [0x2d7d0000, 0x2da46cf0, 0x2da46e00, 0x2e280000)
compacting perm gen total 4096K, used 4095K [0x38280000, 0x38680000, 0x38680000)
the space 4096K, 【99%】 used [0x38280000, 0x3867fff0, 0x38680000, 0x38680000)
ro space 10240K, 44% used [0x38680000, 0x38af73f0, 0x38af7400, 0x39080000)
rw space 12288K, 52% used [0x39080000, 0x396cdd28, 0x396cde00, 0x39c80000)
複製代碼
解決方法:避免動態生成class,增大Perm區,容許Class回收
-Xmx1g -Xss1m
public static class SleepThread implements Runnable{
public void run(){
try {
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String args[]){
for(int i=0;i<1000;i++){
new Thread(new SleepThread(),"Thread"+i).start();
System.out.println("Thread"+i+" created");
}
}
複製代碼
Exception in thread "main" java.lang.OutOfMemoryError:
unable to create new native thread
複製代碼
這裏的棧溢出指,在建立線程的時候,須要爲線程分配棧空間,這個棧空間是向操做系統請求的,若是操做系統沒法給出足夠的空間,就會拋出OOM
eg:堆空間1G,每一個線程棧空間1m
注意:堆+線程棧+直接內存 <= 操做系統可分配空間
ByteBuffer.allocateDirect()
:申請堆外的直接內存-Xmx1g -XX:+PrintGCDetails
//會拋出oom,但堆內存空間充足
for(int i=0;i<1024;i++){
ByteBuffer.allocateDirect(1024*1024);
System.out.println(i);
System.gc();
}
複製代碼
public static List<Integer> numberList =new ArrayList<Integer>();
public static class AddToList implements Runnable{
int startnum=0;
public AddToList(int startnumber){
startnum=startnumber;
}
@Override
public void run() {
int count=0;
while(count<1000000){
numberList.add(startnum);
startnum+=2;
count++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(new AddToList(0));
Thread t2=new Thread(new AddToList(1));
t1.start();
t2.start();
while(t1.isAlive() || t2.isAlive()){
Thread.sleep(1);
}
System.out.println(numberList.size());
}
複製代碼
Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 73
at java.util.ArrayList.add(Unknown Source)
at simpleTest.TestSome$AddToList.run(TestSome.java:27)
at java.lang.Thread.run(Unknown Source)
1000005
複製代碼
ArrayList 不是線程安全的集合對象,在兩個線程添加元素的過程當中,當數組填滿,正在自動擴展時,另外一個線程卻仍是在添加元素,在ArrayList底層就是不可變長的數組,則拋出下表越界異常
HotSpot虛擬機中,對象在內存中存儲的佈局能夠分爲三塊區域:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)。
隨着鎖的競爭,鎖能夠從偏向鎖升級到輕量級鎖,再升級的重量級鎖(可是鎖的升級是單向的,也就是說只能從低到高升級,不會出現鎖的降級)
大多數狀況下鎖不只不存在多線程競爭,並且老是由同一線程屢次得到,爲了讓線程得到鎖的代價更低而引入了偏向鎖。偏向鎖只能在單線程下起做用。
偏向鎖在鎖對象的對象頭中有個ThreadId字段,這個字段若是是空的,第一次獲取鎖的時候,就將自身的ThreadId寫入到鎖的ThreadId字段內,將鎖頭內的是否偏向鎖的狀態位置1.,這樣下次獲取鎖的時候,直接檢查ThreadId是否和自身線程Id一致,若是一致,則認爲當前線程已經獲取了鎖,所以不需再次獲取鎖,略過了輕量級鎖和重量級鎖的加鎖階段。提升了效率。
-XX:+UseBiasedLocking
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
系統啓動後,並不會當即開啓偏向鎖,而是會延遲,能夠設置延遲時間爲0
普通的鎖處理性能不夠理想,輕量級鎖是一種快速的鎖定方法
輕量級鎖是爲了在線程交替執行同步塊時提升性能
若是對象沒有被鎖定
將對象頭的Mark指針保存到鎖對象中
將對象頭設置爲指向鎖的指針(在線程棧空間中)
即對象和鎖都互相保存引用
輕量級鎖加鎖
線程在執行同步塊以前,JVM會先在當前線程的棧楨中建立用於存儲鎖記錄的空間,並將對象頭中的Mark Word複製到鎖記錄中,官方稱爲Displaced Mark Word。
而後線程嘗試使用CAS將對象頭中的Mark Word替換爲指向鎖記錄的指針。若是成功,當前線程得到鎖,若是失敗,表示其餘線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。
輕量級鎖解鎖
輕量級解鎖時,會使用原子的CAS操做來將Displaced Mark Word替換回到對象頭,若是成功,則表示沒有競爭發生。
若是失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖
lock位於線程棧中
由上可知,判斷一個線程是否持有輕量級鎖,只要判斷對象頭的指針,是否在線程的棧空間範圍內
特性
-XX:+UseSpinning
開啓當發生爭用時,若Owner線程能在很短的時間內釋放鎖,則那些正在爭用線程(未阻塞)能夠稍微等一等(自旋),在Owner線程釋放鎖後,爭用線程可能會當即獲得鎖,從而避免線程阻塞
鎖 | 優勢 | 缺點 | 適用場景 |
---|---|---|---|
偏向鎖 | 加鎖和解鎖不須要額外的消耗,和執行非同步方法比僅存在納秒級的差距。 | 若是線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗。 | 適用於只有一個線程訪問同步塊場景。 |
輕量級鎖 | 競爭的線程不會阻塞,提升了程序的響應速度。 | 若是始終得不到鎖競爭的線程使用自旋會消耗CPU。有競爭時會比重量級鎖更慢 | 追求響應時間。同步塊執行速度很是快。 |
重量級鎖 | 線程競爭不使用自旋,不會消耗CPU。 | 線程阻塞,響應時間緩慢。 | 追求吞吐量。同步塊執行速度較長。 |
偏向鎖與輕量級鎖理念上的區別:
輕量級鎖:在無競爭的狀況下使用CAS操做去消除同步使用的互斥量
偏向鎖:在無競爭的狀況下把整個同步都消除掉
連CAS操做都不作了?
同步範圍減小
將大對象拆成小對象,增長並行度,下降鎖競爭
偏向鎖和輕量級鎖成功率提升——粒度大,競爭激烈,偏向鎖,輕量級鎖失敗機率就高
Segment<K,V>[] segments
HashEntry<K,V>
鎖類型 | 讀鎖 | 寫鎖 |
---|---|---|
讀鎖 | 可訪問 | 不可訪問 |
寫鎖 | 不可訪問 | 不可訪問 |
若是對同一個鎖不停的進行請求、同步和釋放,其自己也會消耗系統寶貴的資源,反而不利於性能的優化
public void demoMethod(){
synchronized(lock){
//do sth.
}
//作其餘不須要的同步的工做,但能很快執行完畢
synchronized(lock){
//do sth.
}
}
複製代碼
直接擴大範圍
public void demoMethod(){
//整合成一次鎖請求
synchronized(lock){
//do sth.
//作其餘不須要的同步的工做,但能很快執行完畢
}
}
複製代碼
for(int i=0;i<CIRCLE;i++){
synchronized(lock){
}
}
//鎖粗化
synchronized(lock){
for(int i=0;i<CIRCLE;i++){
}
}
複製代碼
在即時編譯器時,若是發現不可能被共享的對象,則能夠消除這些對象的鎖操做
鎖不是由程序員引入的,JDK自帶的一些庫,可能內置鎖
棧上對象,不會被全局訪問的,沒有必要加鎖
public static void main(String args[]) throws InterruptedException {
long start = System.currentTimeMillis();
for (int i = 0; i < CIRCLE; i++) {
craeteStringBuffer("JVM", "Diagnosis");
}
long bufferCost = System.currentTimeMillis() - start;
System.out.println("craeteStringBuffer: " + bufferCost + " ms");
}
public static String craeteStringBuffer(String s1, String s2) {
//StringBuffer線程安全對象,內置鎖
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}
複製代碼
-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks
無鎖的一種實現方式 CAS(Compare And Swap)
非阻塞的同步
CAS(V,E,N):if V==E then V=N
複製代碼
CAS算法的過程: CAS(V,E,N)。V表示要更新的變量,E表示預期值,N表示新值。 僅當V值等於E值時,纔會將V的值設爲N,若是V值和E值不一樣,則說明已經有其餘線程作了更新,則當前線程什麼都不作。
最後,CAS返回當前V的真實值。
CAS操做是抱着樂觀的態度進行的,它老是認爲本身能夠成功完成操做。當多個線程同時使用CAS操做一個變量時,只有一個會勝出,併成功更新,其他均會失敗。失敗的線程不會被掛起,僅是被告知失敗,而且容許再次嘗試,固然也容許失敗的線程放棄操做。基於這樣的原理,CAS操做即時沒有鎖,也能夠發現其餘線程對當前線程的干擾,並進行恰當的處理。
java.util.concurrent.atomic包使用無鎖實現,性能高於通常的有鎖操做
當多個線程同時請求某個對象監視器時,對象監視器會設置幾種狀態用來區分請求的線程:
U4:無符號整型,4個字節
類型 | 名稱 | 數量 | 備註 |
---|---|---|---|
u4 | magic | 1 | 0xCAFEBABE:表示java class文件類型 |
u2 | minor_version | 1 | Jdk編譯版本 |
u2 | major_version | 1 | Jdk編譯版本 |
u2 | constant_pool_count | 1 | |
cp_info | constant_pool | constant_pool_count - 1 | 鏈式引用基本類型-被各處引用-要減1 |
u2 | access_flags | 1 | 訪問修飾符&class type |
u2 | this_class | 1 | 指向常量池的class |
u2 | super_class | 1 | 指向常量池的class |
u2 | interfaces_count | 1 | |
u2 | interfaces | interfaces_count | 每一個接口指向常量池CONSTANT_Class索引 |
u2 | fields_count | 1 | |
field_info | fields | fields_count | access_flags,name_index ,descriptor_index ,attributes_count,attribute_info attributes[attributes_count] |
u2 | methods_count | 1 | |
method_info | methods | methods_count | |
u2 | attribute_count | 1 | |
attribute_info | attributes | attributes_count |
線程幀棧中的數據:
-XX:CompileThreshold=1000
:執行超過一千次即爲熱點代碼-XX:+PrintCompilation
:打印編譯爲機器碼的代碼-Xint
:解釋執行-Xcomp
:所有編譯執行-Xmixed
:默認,混合