Java虛擬機學習筆記整理

本文主要整理自煉術成金JVM教學資料和《深刻理解Java虛擬機》,部分資料整理自網絡,已不明來源java

一. JVM規範

1.1 位運算

1.1.1 整型int

  • 原碼:第一位符號位,0爲正,1爲負
  • 反碼:符號位不動,原碼取反
  • 補碼
    • 正數補碼:和源碼相同
    • 負數補碼:符號位不動,反碼加1

example程序員

-6
原碼: 10000110
反碼: 11111001
補碼: 11111010
複製代碼
  • 爲什麼使用補碼
    • 能夠無歧義地表示0

不使用補碼,將0看爲算法

正數:0000 0000  
負數:1000 0000  
複製代碼

則不一致
使用補碼:數組

負數:1000 0000  
反碼:1111 111
補碼:0000 0000 = 正數 
複製代碼

正數和負數使用補碼作運算至關於用加法作運算
計算時都是使用補碼進行計算
緩存

補碼示例

1.1.2 單精度Float

  • 表示方式
    單精度

當指數位tomcat

  • 全爲0,尾數附加位爲0
  • 不全爲0,則尾數附加位爲1
    如此,尾數位就湊足了24位

計算方式 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安全

二.JVM運行機制

2.1 JVM啓動流程

JVM啓動流程

2.2 JVM基本結構

JVM基本結構

方法區物理上存在於堆裏,並且是在堆的持久代裏面;但在邏輯上,方法區和堆是獨立的 方法區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編譯後的代碼緩存),每一個類結構(如運行時常數池,字段和方法數據)以及方法和構造方法的代碼都在非堆內存中.

2.2.1 PC寄存器

  1. 每個線程擁有一個PC寄存器
  2. 在線程建立時建立
  3. 指向下一條指令
  4. 執行本地方法時,PC值爲undefined ?

2.2.2 方法區

  1. 保存裝載的類信息:字段、方法信息、方法字節碼
  2. 一般和永久區(perm)關聯在一塊兒

2.2.3 Java堆

  1. 對象保存在堆中
  2. 全部線程共享java堆
  3. GC工做空間
    GC工做空間

2.2.4 Java棧

  1. 線程私有

  2. 棧由一系列幀組成(故也叫幀棧)

  3. 幀保存每一個方法的局部變量表,操做數棧,常量池指針,程序計數器

  4. 每一次方法調用建立一個幀,並壓棧

  5. 幀中有局部變量表

    static方法

    no static方法

  6. 操做數棧
    Java沒有寄存器,全部參數傳遞使用操做數棧

    操做數棧

    棧上分配空間

  7. 小對象(幾十bytes),在沒有逃逸的狀況下,能夠直接分配在棧上

  8. 直接分配在棧上,能夠自動回收,減輕GC壓力

  9. 大對象或逃逸對象沒法在棧上分配
    逃逸對象:棧內對象被外部對象引用,其做用範圍脫離了當前方法棧

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 關鍵字

3.1 內存模型特性

  • 可見性:一個線程修改了變量,其餘線程能夠當即知道
  • 保證可見性的方法:
  1. volatile
  2. synchronized(unlock以前,寫變量值回主存)
  3. final(一旦初始化完成,其餘線程就可見)
  • 有序性 在本線程內,操做是有序的 在線程外觀察,操做是無序的(指令重排 或 主內存與線程內存同步延期)
  • 指令重排 爲了提升程序運行效率,調整指令執行次序 與寫相鄰的指令不可重排:讀後寫,寫後讀,寫後寫 編譯器不考慮多線程間的語義
  • 指令重排 – 破壞線程間的有序性
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

線程重排
在writer中,兩句話順序可能打亂

  • 指令重排 – 保證有序性的方法 對方法加上同步關鍵字synchronized
  • 指令重排的基本原則
    1. 程序順序原則:一個線程內保證語義的串行性
    2. volatile規則:volatile變量的寫,先發生於讀
    3. 鎖規則:解鎖(unlock)必然發生在隨後的加鎖(lock)前
    4. 傳遞性:A先於B,B先於C 那麼A必然先於C
    5. 線程的start方法先於它的每個動做和/方法
    6. 線程的全部操做先於線程的終結Thread.join(),最後才終結
    7. 線程的中斷interrupt()先於被中斷線程的代碼,中斷當即中止
    8. 對象的構造函數執行結束先於finalize()方法

3.2 經常使用JVM參數配置

  • Tract跟蹤參數
    -XX:+TraceClassLoading:監控類的加載
    -XX:+PrintClassHistogram: 按下Ctrl+Break後,打印類的信息
  • 堆的分配參數
    XX:+HeapDumpOnOutOfMemoryError:OOM時導出堆到文件
    -XX:OnOutOfMemoryError: 在OOM時,執行一個腳本
    官方推薦新生代佔堆的3/8
    倖存代佔新生代的1/10
  • 棧的分配參數
    Xss
    • 一般只有幾百K
    • 決定了函數調用的深度
    • 每一個線程都有獨立的棧空間
    • 局部變量、參數 分配在棧上

四.GC的算法與種類

4.1 GC算法

  1. 引用計數法:java中未使用
  2. 標記清除:老年代
  3. 標記壓縮:老年代
  4. 複製算法:新生代
  5. 分代思想
    • 依據對象的存活週期進行分類,短命對象歸爲新生代,長命對象歸爲老年代
    • 根據不一樣代的特色,選取合適的收集算法
      • 少許對象存活,適合複製算法
      • 大量對象存活,適合標記清理或者標記壓縮

4.2 可觸及性

  1. 可觸及的
  • 從根節點能夠觸及到這個對象
  • 根:(與方法區棧相關)
    • 棧中引用的對象
    • 方法區中靜態成員或者常量引用的對象(全局對象)
    • JNI方法棧中引用對象
  1. 可復活的
  • 一旦全部引用被釋放,就是可復活狀態,即不可達
  • 但在finalize()中可能復活該對象
  1. 不可觸及的
    • 在finalize()後,可能會進入不可觸及狀態
    • 不可觸及的對象不可能復活
    • 能夠回收
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";
    }
}
複製代碼
  1. 避免使用finalize方法
  2. 對象中只能調用一次,操做不慎可能致使錯誤
  3. 優先級低,什麼時候被調用不肯定,什麼時候發生GC不肯定
  4. 可使用try-catch-finally來替代

對於用可達性分析法搜索不到的對象,GC並不必定會回收該對象。要徹底回收一個對象,至少須要通過兩次標記的過程。
第一次標記:對於一個沒有其餘引用的對象,篩選該對象是否有必要執行finalize()方法,若是沒有執行必要,則意味可直接回收。(篩選依據:是否複寫或執行過finalize()方法;由於finalize方法只能被執行一次)。
第二次標記:若是被篩選斷定位有必要執行,則會放入FQueue隊列,並自動建立一個低優先級的finalize線程來執行釋放操做。若是在一個對象釋放前被其餘對象引用,則該對象會被移除FQueue隊列

4.3 Stop-The-World

Java中一種全局暫停的現象
全局停頓,全部Java代碼中止,native代碼能夠執行,但不能和JVM交互
多半因爲GC引發,也能夠是Dump線程、死鎖檢查、堆Dump

4.4 串行蒐集器

  1. 最古老,最穩定
  2. 效率高
  3. 可能會產生較長的停頓
  4. 適用於數據量較小,對響應時間無要求的小型應用
  5. -XX:+UseSerialGC
    • 新生代、老年代使用串行回收
    • 新生代複製算法
    • 老年代標記-壓縮
      串行蒐集器

4.5 並行收集器

適用於對吞吐量有較高要求, 多CPU、對應用響應時間無要求的中、大型應用。
舉例:後臺處理、科學計算 吞吐量:=運行用戶代碼時間/(運行用戶代碼時間+GC時間)

  • 併發Concurrent:交替作不一樣事的能力,用戶程序能夠不暫停,不必定並行,但能夠交替執行
  • 並行Parallel:同時作不一樣事的能力,垃圾回收線程並行工做,但應用程序等待暫停

4.5.1 ParNew

  • -XX:+UseParNewGC
    • 新生代並行
    • 老年代串行
  • Serial收集器新生代的並行版本
  • 複製算法
  • 多線程,須要多核支持
  • -XX:ParallelGCThreads 限制線程數量
    ParNew
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]
複製代碼

4.5.2 Parallel收集器(可自定義的、靈活)

  1. 相似ParNew
  2. 新生代複製算法
  3. 老年代 標記-壓縮
  4. 更加關注吞吐量
  5. -XX:+UseAdaptiveSizePolicy自適應調節策略是Parallel與ParNew的重要區別
  6. -XX:+UseParallelGC
    • 新生代使用Parallel收集器+ 老年代串行
  7. -XX:+UseParallelOldGC
    • 新生代使用Parallel收集器+ 老年代並行

老年代不同而已

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]
複製代碼
  1. 特殊參數
    • -XX:MaxGCPauseMills
      • 最大停頓時間,單位毫秒
      • GC盡力保證回收時間不超過設定值
    • -XX:GCTimeRatio
      • 0-100的取值範圍
      • 垃圾收集時間佔總時間的比
      • 默認99,即最大容許1%時間作GC
    • 這兩個參數是矛盾的。由於停頓時間和吞吐量不可能同時調優

4.6 CMS併發收集器

適用於對響應時間有高要求,多CPU、對應用響應時間有較高要求的中、大型應用。
舉例:Web服務器/應用服務器、電信交換、集成開發環境

  • 特性
    Concurrent Mark Sweep 併發標記清除(與用戶線程一塊兒執行 )
    標記-清除算法(不是標記壓縮)
    併發階段會下降吞吐量(?)
    只是針對老年代收集器(新生代使用ParNew/或串行)
    -XX:+UseConcMarkSweepGC

  • 運行過程

    1. 初始標記
      根能夠直接關聯到的對象
      速度快
      獨佔CPU,全局停頓
    2. 併發標記(和用戶線程一塊兒)
      標記的主要過程,標記所有對象
    3. 從新標記
      從新修正標記
      獨佔CPU,全局停頓
    4. 併發清除(和用戶線程一塊兒)
      基於標記結果,直接清除對象
      CMS併發收集器
  • 優:
    儘量下降停頓,在併發標記過程當中並不須要全局停頓

  • 劣:

  1. 會影響系統總體吞吐量和性能
    • 好比,在用戶線程運行過程當中,分一半CPU去作GC,系統性能在GC階段,反應速度就降低一半
  2. 清理不完全
    • 在清理階段,用戶線程還在運行,會產生新的垃圾,沒法清理,由於和用戶線程一塊兒運行,不能在空間快滿時再清理
    • -XX:CMSInitiatingOccupancyFraction設置觸發GC的閾值
    • 若是不幸內存預留空間不夠,就會引發concurrent mode failure,此時應該使用串行收集器做爲後備,因爲空間不足,此時通常停頓時間較長
  • 碎片清理問題
    CMS使用的是標記-清除算法,在清除後堆內存有效對象地址不連續,有內存碎片存在,故可設置內存壓縮,整理內存碎片 即CMS爲了性能考慮在老年代使用標記-清除算法,但仍能夠設置使用標記-壓縮算法
  1. -XX:+ UseCMSCompactAtFullCollectionFull GC後,進行一次整理
    • 整理過程是獨佔的,會引發停頓時間變長
  2. -XX:+CMSFullGCsBeforeCompaction
    • 設置進行幾回Full GC後,進行一次碎片整理
  3. -XX:ParallelCMSThreads
    • 設定CMS的線程數量,通常大約設成cpu核數,默認定義爲(CPU數量+3)/4,即至少25%

4.7 GC參數整理

4.7.1 內存分配

參數名稱 含義 備註
-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時無效, 另外一種直接在舊生代分配的狀況是大的數組對象,且數組中無外部引用對象.

4.7.2 並行收集器相關參數

參數名稱 含義 備註
-XX:+UseParallelGC 新生代使用Parallel收集器+ 老年代串行
-XX:+UseParNewGC 在新生代使用並行收集器
-XX:ParallelGCThreads 並行收集器的線程數 此值最好配置與處理器數目相等 也適用於CMS
-XX:+UseParallelOldGC 新生代使用Parallel收集器+ 老年代並行
-XX:MaxGCPauseMillis 每次年輕代垃圾回收的最長時間(最大暫停時間) 若是沒法知足此時間,JVM會自動調全年輕代大小,以知足此值
-XX:+UseAdaptiveSizePolicy 自動選擇年輕代區大小和相應的Survivor區比例 設置此選項後,並行收集器會自動選擇年輕代區大小和相應的Survivor區比例,以達到目標系統規定的最低相應時間或者收集頻率等,此值建議使用並行收集器時,一直打開.

4.7.3 CMS併發相關參數

參數名稱 含義 備註
-XX:+UseConcMarkSweepGC 使用CMS內存收集 新生代使用並行收集器ParNew,老年代使用CMS+串行收集器
-XX:CMSFullGCsBeforeCompaction 多少次後進行內存壓縮 因爲併發收集器不對內存空間進行壓縮,整理,因此運行一段時間之後會產生"碎片",使得運行效率下降.此值設置運行多少次GC之後對內存空間進行壓縮,整理
-XX+UseCMSCompactAtFullCollection 在FULL GC的時候, 對年老代的壓縮 CMS是不會移動內存的, 所以, 這個很是容易產生碎片, 致使內存不夠用, 所以, 內存的壓縮這個時候就會被啓用。 增長這個參數是個好習慣。可能會影響性能,可是能夠消除碎片
-XX:CMSInitiatingPermOccupancyFraction 當永久區佔用率達到這一百分比時,啓動CMS回收

4.7.4 輔助信息

參數名稱 含義
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCApplicationStoppedTime 打印垃圾回收期間程序暫停的時間.可與上面混合使用
-XX:+PrintHeapAtGC 打印GC先後的詳細堆棧信息
--Xloggc:filename 把相關日誌信息記錄到文件以便分析
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath
-XX:+PrintCommandLineFlags 打印出已經被設置過的詳細的 XX 參數的名稱和值

4.8 調優總結

項目 響應時間優先 吞吐量優先
年輕代 -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:打開對年老代的壓縮。可能會影響性能,可是能夠消除碎片

4.9 GC日誌

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收集器)

4.10 GC觸發條件

觸發條件就是某GC算法對應區域滿了,或是預測快滿了(好比該區使用比例達到必定比例-對並行/併發,或不夠晉升)

4.10.1 GC分類

針對HotSpot VM的實現,它裏面的GC其實準確分類只有兩大種:

  • Partial GC:並不收集整個GC堆的模式
    Young GC:只收集young gen的GC
    Old GC:只收集old gen的GC。只有CMS的concurrent collection是這個模式
    Mixed GC:收集整個young gen以及部分old gen的GC。只有G1有這個模式
  • Full GC
    收集整個堆,包括young gen、old gen、perm gen(若是存在的話)等全部部分的模式。收集是總體收集的,無所謂先收集old仍是young。marking是總體一塊兒作的,而後compaction(壓縮)是old gen先來而後再young gen來

4.10.2 HotSpot VM的serial GC

Major GC一般是跟full GC是等價的,收集整個GC堆。最簡單的分代式GC策略,按HotSpot VM的serial GC的實現來看,觸發條件是:

  • young GC:當young gen中的eden區分配滿的時候觸發。注意young GC中有部分存活對象會晉升到old gen,因此young GC後old gen的佔用量一般會有所升高。
  • full GC
    1. 當準備要觸發一次young GC時,若是發現統計數聽說以前young GC的平均晉升大小比目前old gen剩餘的空間大,則不會觸發young GC而是轉爲觸發full GC(由於HotSpot VM的GC裏,除了CMS的concurrent collection以外,其它能收集old gen的GC都會同時收集整個GC堆,包括young gen,因此不須要事先觸發一次單獨的young GC);
    2. 若是有perm gen的話,要在perm gen分配空間但已經沒有足夠空間時,也要觸發一次full GC;
    3. System.gc()、heap dump帶GC,默認也是觸發full GC。

4.10.3 HotSpot VM非併發GC(Parallel GC)

觸發條件複雜一些,不過大體的原理與串行GC同樣。
例外: Parallel Scavenge(-XX:+UseParallelGC新生代使用Parallel收集器)框架下,默認是在要觸發full GC前先執行一次young GC,而且兩次GC之間能讓應用程序稍微運行一下,以期下降full GC的暫停時間(由於young GC會盡可能清理了young gen的死對象,減小了full GC的工做量)。控制這個行爲的VM參數是-XX:+ScavengeBeforeFullGC

4.10.4 HotSpot VM併發GC

併發GC的觸發條件就不太同樣。以CMS GC爲例,主要是定時去檢查old gen的使用量,當使用量超過了觸發比例就會啓動一次CMS GC,對old gen作併發收集

-XX:CMSInitiatingOccupancyFraction=80 // old達到80%收集
複製代碼

或者GC過程當中,因爲預留的內存沒法知足程序須要, 出現concurrent mode failure,臨時使用serial old進行Full GC

4.10.5 HotSpot VM G1收集

G1 GC的initial marking(初始標記)的觸發條件是Heap使用比率超過某值,收集時是按照回收價值的優先級,不按照young old區

G1 GC:Young GC + mixed GC(新生代,再加上部分老生代)+ Full GC for G1 GC算法(應對G1 GC算法某些時候的不趕趟,開銷很大);

五. 類裝載器

5.1 Class裝載驗證流程

5.1.1 加載

轉爲方法區數據結構 在Java堆中生成對應的java.lang.Class對象

  • 類裝載器ClassLoader
  • ClassLoader是一個抽象類
    • ClassLoader的實例將讀入Java字節碼將類裝載到JVM中
    • ClassLoader能夠定製,知足不一樣的字節碼流獲取方式(好比網絡)

jdk默認類加載過程

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

5.1.2 連接

  • 驗證 目的:保證Class流的格式是正確的
    1. 文件格式的驗證
      • 是否以0xCAFEBABE開頭
      • 版本號是否合理:class文件由什麼版本jdk編譯生成,與執行class的jdk是否兼容
    2. 元數據驗證(基本信息驗證)
      • 是否有父類:class中指定了父類,檢查父類是否存在
      • 繼承了final類?
      • 非抽象類實現了全部的抽象方法
    3. 字節碼驗證 (複雜)
      • 運行檢查
      • 棧數據類型和操做碼數據參數吻合
      • 跳轉指令指定到合理的位置
    4. 符號引用驗證
      • 常量池中描述類是否存在:引用的類必須存在
      • 訪問的方法或字段是否存在且有足夠的權限:private…
  • 準備
    1. 分配內存,併爲類設置初始值 (方法區中)
      • public static int v=1;
      • 在準備階段中,v會被設置爲0
      • 在初始化的中才會被設置爲1
      • 對於static final類型,在準備階段就會被賦上正確的值—在初始化以前就賦值
      • public static final int v=1;
  • 解析 符號引用替換爲直接引用:即類名應用,直接替換爲內存地址指針

5.1.3 初始化

  • 執行類構造器
    • static變量 賦值語句 : 注意,static final 在準備階段已經賦值了
    • static{}語句
  • 子類的調用前保證父類的被調用
  • 是線程安全的,即單線程執行

六. 性能分析

6.1 Java自帶性能分析的工具

直接在控制檯輸入命令,參數具體使用可以使用-help 命令

6.1.1 jps

通常是第一步,方便後續其餘命令調用
列出java進程,相似於ps命令
參數-q能夠指定jps只輸出進程ID ,不輸出類的短名稱
參數-m能夠用於輸出傳遞給Java進程(主函數)的參數
參數-l能夠用於輸出主函數的完整路徑
參數-v能夠顯示傳遞給JVM的參數

jps命令

6.1.2 jinfo

查看進程參數 能夠用來查看正在運行的Java應用程序的擴展參數,甚至支持在運行時,修改部分參數
-flag 進程ID:打印指定JVM的參數值
-flag [+|-] 進程ID:設置指定JVM參數的布爾值
-flag = 進程ID:設置指定JVM參數的值

jinfo

6.1.3 jmap

生成Java應用程序的堆快照和對象的統計信息

jmap

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
複製代碼

6.1.4 jstack

打印線程dump
-l 打印鎖信息
-m 打印java和native的幀信息
-F 強制dump,當jstack沒有響應時使用
Jdk1.6版本只有 –l選項

jstack

6.1.5 JConsole

圖形化監控工具
能夠查看Java應用程序的運行概況,監控堆信息、永久區使用狀況、類加載狀況等

JConsole

6.1.6 Visual VM

Visual VM是一個功能強大的多合一故障診斷和性能監控的可視化工具

Visual VM

6.1.7 MAT

MAT

6.2 Java堆分析

  • 內存溢出OOM緣由
    Jvm內存區間:堆、永久區、線程棧、直接內存
    堆+線程棧 +直接內存<= 操做系統可分配空間
  1. 堆溢出
    佔用大量堆空間,直接溢出
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)
複製代碼

解決方法:增大堆空間,及時釋放內存,分批處理

  1. 永久區溢出
//生成大量的類
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回收

  1. Java棧溢出
    -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

注意:堆+線程棧+直接內存 <= 操做系統可分配空間

  1. 直接內存溢出
    ByteBuffer.allocateDirect():申請堆外的直接內存
    直接內存也能夠被GC回收
    -Xmx1g -XX:+PrintGCDetails
//會拋出oom,但堆內存空間充足
for(int i=0;i<1024;i++){
    ByteBuffer.allocateDirect(1024*1024);
    System.out.println(i);
      System.gc();
}
複製代碼

七. 鎖

7.1 線程安全

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底層就是不可變長的數組,則拋出下表越界異常

7.2 對象頭Mark

HotSpot虛擬機中,對象在內存中存儲的佈局能夠分爲三塊區域:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)。

對象內存結構
Mark Word 32bit

7.3 偏向鎖

隨着鎖的競爭,鎖能夠從偏向鎖升級到輕量級鎖,再升級的重量級鎖(可是鎖的升級是單向的,也就是說只能從低到高升級,不會出現鎖的降級)
大多數狀況下鎖不只不存在多線程競爭,並且老是由同一線程屢次得到,爲了讓線程得到鎖的代價更低而引入了偏向鎖。偏向鎖只能在單線程下起做用
偏向鎖在鎖對象的對象頭中有個ThreadId字段,這個字段若是是空的,第一次獲取鎖的時候,就將自身的ThreadId寫入到鎖的ThreadId字段內,將鎖頭內的是否偏向鎖的狀態位置1.,這樣下次獲取鎖的時候,直接檢查ThreadId是否和自身線程Id一致,若是一致,則認爲當前線程已經獲取了鎖,所以不需再次獲取鎖,略過了輕量級鎖和重量級鎖的加鎖階段。提升了效率。

  1. 大部分狀況是沒有競爭的,因此能夠經過偏向來提升性能
  2. 所謂的偏向,就是偏愛,即鎖會偏向於當前已經佔有鎖的線程
  3. 將對象頭Mark的標記設置爲偏向,並將線程ID寫入對象頭Mark
  4. 只要沒有競爭,得到偏向鎖的線程,在未來進入同步塊,不須要作同步
  5. 當其餘線程請求相同的鎖時,偏向模式結束,在全局安全點(在這個時間點上沒有字節碼正在執行)撤銷偏向鎖,採用其餘鎖
  6. -XX:+UseBiasedLocking
    • 默認啓用
    • 在競爭激烈的場合,偏向鎖會增長系統負擔
      開啓偏向鎖 -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 系統啓動後,並不會當即開啓偏向鎖,而是會延遲,能夠設置延遲時間爲0
      偏向鎖的得到和撤銷流程

7.4 輕量級鎖

普通的鎖處理性能不夠理想,輕量級鎖是一種快速的鎖定方法
輕量級鎖是爲了在線程交替執行同步塊時提升性能

  • 若是對象沒有被鎖定
    將對象頭的Mark指針保存到鎖對象中
    將對象頭設置爲指向鎖的指針(在線程棧空間中)
    即對象和鎖都互相保存引用

    輕量級鎖加鎖
    線程在執行同步塊以前,JVM會先在當前線程的棧楨中建立用於存儲鎖記錄的空間,並將對象頭中的Mark Word複製到鎖記錄中,官方稱爲Displaced Mark Word。
    而後線程嘗試使用CAS將對象頭中的Mark Word替換爲指向鎖記錄的指針。若是成功,當前線程得到鎖,若是失敗,表示其餘線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。

    輕量級鎖解鎖
    輕量級解鎖時,會使用原子的CAS操做來將Displaced Mark Word替換回到對象頭,若是成功,則表示沒有競爭發生。
    若是失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖

  • lock位於線程棧中
    由上可知,判斷一個線程是否持有輕量級鎖,只要判斷對象頭的指針,是否在線程的棧空間範圍內

  • 特性

    • 若是輕量級鎖失敗,表示存在競爭,升級爲重量級鎖(常規鎖,操做系統,進程級)
    • 在沒有鎖競爭的前提下,減小傳統鎖使用OS互斥量產生的性能損耗
    • 在競爭激烈時,輕量級鎖會多作不少額外操做,致使性能降低
      偏向鎖升級進入輕量級鎖
      mark word中的lock record指向堆棧最近的一個線程的lock record,其實就是按照先來後到模式進行了輕量級的加鎖
      輕量級鎖及膨脹流程

7.5 自旋鎖 spin lock

  • 儘可能減小系統級別的線程掛起
    • 當競爭存在時,若是線程能夠很快得到鎖,那麼能夠不在OS層掛起線程,讓線程作幾個空操做(自旋)等待得到鎖
    • JDK1.6中-XX:+UseSpinning開啓
    • JDK1.7中,去掉此參數,改成內置實現
    • 若是同步塊很長,自旋失敗,會下降系統性能—空佔線程操做,最後仍是要在OS層掛起,自旋鎖空耗資源
    • 若是同步塊很短,自旋成功,節省線程掛起切換時間,提高系統性能

當發生爭用時,若Owner線程能在很短的時間內釋放鎖,則那些正在爭用線程(未阻塞)能夠稍微等一等(自旋),在Owner線程釋放鎖後,爭用線程可能會當即獲得鎖,從而避免線程阻塞

7.6 偏向鎖vs輕量級鎖vs自旋鎖

  • 不是Java語言層面的鎖優化方法
  • 內置於JVM中的獲取鎖的優化方法和獲取鎖的步驟
    • 偏向鎖可用會先嚐試偏向鎖
    • 輕量級鎖可用會先嚐試輕量級鎖
    • 以上都失敗,嘗試自旋鎖
    • 再失敗,嘗試普通鎖(重量級鎖),使用OS互斥量在操做系統層掛起
優勢 缺點 適用場景
偏向鎖 加鎖和解鎖不須要額外的消耗,和執行非同步方法比僅存在納秒級的差距。 若是線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗。 適用於只有一個線程訪問同步塊場景。
輕量級鎖 競爭的線程不會阻塞,提升了程序的響應速度。 若是始終得不到鎖競爭的線程使用自旋會消耗CPU。有競爭時會比重量級鎖更慢 追求響應時間。同步塊執行速度很是快。
重量級鎖 線程競爭不使用自旋,不會消耗CPU。 線程阻塞,響應時間緩慢。 追求吞吐量。同步塊執行速度較長。

偏向鎖與輕量級鎖理念上的區別:
輕量級鎖:在無競爭的狀況下使用CAS操做去消除同步使用的互斥量
偏向鎖:在無競爭的狀況下把整個同步都消除掉

連CAS操做都不作了?

7.7 Java語言層面優化鎖

7.7.1 減小鎖持有時間

同步範圍減小

7.7.2 減少鎖粒度

將大對象拆成小對象,增長並行度,下降鎖競爭
偏向鎖和輕量級鎖成功率提升——粒度大,競爭激烈,偏向鎖,輕量級鎖失敗機率就高

  • ConcurrentHashMap
    若干個Segment :Segment<K,V>[] segments
    Segment中維護HashEntry<K,V>
    put操做時
    先定位到Segment,鎖定一個Segment,執行put
    在減少鎖粒度後, ConcurrentHashMap容許若干個線程同時進入

7.7.3 鎖分離

  • 讀寫鎖ReadWriteLock
鎖類型 讀鎖 寫鎖
讀鎖 可訪問 不可訪問
寫鎖 不可訪問 不可訪問
  • LinkedBlockingQueue 只要操做互不影響,鎖就能夠分離
    隊列

7.7.4 鎖粗化

若是對同一個鎖不停的進行請求、同步和釋放,其自己也會消耗系統寶貴的資源,反而不利於性能的優化

  • Example1:
public void demoMethod(){
	synchronized(lock){
		//do sth.
	}
	//作其餘不須要的同步的工做,但能很快執行完畢
	synchronized(lock){
		//do sth.
	}
}
複製代碼

直接擴大範圍

public void demoMethod(){
	//整合成一次鎖請求
	synchronized(lock){
		//do sth.
		//作其餘不須要的同步的工做,但能很快執行完畢
	}
}
複製代碼
  • Example2
for(int i=0;i<CIRCLE;i++){
	synchronized(lock){
		
	}
}

//鎖粗化
synchronized(lock){
for(int i=0;i<CIRCLE;i++){
		
	}
}
複製代碼

7.7.5 鎖消除

在即時編譯器時,若是發現不可能被共享的對象,則能夠消除這些對象的鎖操做
鎖不是由程序員引入的,JDK自帶的一些庫,可能內置鎖
棧上對象,不會被全局訪問的,沒有必要加鎖

  • Example
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

  • 棧上對象(方法局部變量),不會被全局訪問的,沒有必要加鎖

7.7.6 無鎖

無鎖的一種實現方式 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包使用無鎖實現,性能高於通常的有鎖操做

7.8 線程狀態及裝換

當多個線程同時請求某個對象監視器時,對象監視器會設置幾種狀態用來區分請求的線程:

  • Contention List:全部請求鎖的線程將被首先放置到該競爭隊列
  • Entry List:Contention List中那些有資格成爲候選人的線程被移到Entry List
  • Wait Set:那些調用wait方法被阻塞的線程被放置到Wait Set
  • OnDeck:任什麼時候刻最多隻能有一個線程正在競爭鎖,該線程稱爲OnDeck
  • Owner:得到鎖的線程稱爲Owner
  • !Owner:釋放鎖的線程
    線程狀態轉換
    那些處於ContetionList、EntryList、WaitSet中的線程均處於阻塞狀態,阻塞操做由操做系統完成(在Linxu下經過pthread_mutex_lock函數)。
    線程被阻塞後便進入內核(Linux)調度狀態,這個會致使系統在用戶態與內核態之間來回切換,嚴重影響鎖的性能
  • Synchronized加鎖
    每個線程在準備獲取共享資源時:
  1. 檢查MarkWord裏面是否是放的本身的ThreadId ,若是是,表示當前線程是處於 「偏向鎖」
  2. 若是MarkWord不是本身的ThreadId,鎖升級,這時候,用CAS來執行切換,新的線程根據MarkWord裏面現有的ThreadId,通知以前線程暫停,以前線程將Markword的內容置爲空。
  3. 兩個線程都把對象的HashCode複製到本身新建的用於存儲鎖的記錄空間,接着開始經過CAS操做,把共享對象的MarKword的內容修改成本身新建的記錄空間的地址的方式競爭MarkWord,
  4. 第三步中成功執行CAS的得到資源,失敗的則進入自旋
  5. 自旋的線程在自旋過程當中,成功得到資源(即以前獲的資源的線程執行完成並釋放了共享資源),則整個狀態依然處於 輕量級鎖的狀態,若是自旋失敗
  6. 進入重量級鎖的狀態,這個時候,自旋的線程進行阻塞,等待以前線程執行完成並喚醒本身

八.Class文件結構

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

Class文件結構

九. JVM字節碼執行

9.1 javap

線程幀棧中的數據:

  • 程序計數器:每一個線程都有一個,用於指向當前線程執行的指令地
  • 局部變量表
  • 操做數棧

9.2 JIT及其相關參數

  • JIT Just-In-Time
    字節碼執行性能較差,因此能夠對於熱點代碼(Hot Spot Code)編譯成機器碼再執行,在運行時的編譯
    當虛擬機發現某個方法或代碼塊運行特別頻繁時,就會把這些代碼認定爲「Hot Spot Code」(熱點代碼),爲了提升熱點代碼的執行效率,在運行時,虛擬機將會把這些代碼編譯成與本地平臺相關的機器碼)
  • 辨別熱點代碼
    方法調用計數器:方法調用次數
    回邊計數器:方法內循環次數,能夠在棧上直接替換爲機器碼
  • 編譯設置
    -XX:CompileThreshold=1000 :執行超過一千次即爲熱點代碼
    -XX:+PrintCompilation :打印編譯爲機器碼的代碼
    -Xint:解釋執行
    -Xcomp:所有編譯執行
    -Xmixed:默認,混合
相關文章
相關標籤/搜索