1. 程式人生 > >頂級架構師學習——第三階段:深入JVM核心——原理、診斷與優化

頂級架構師學習——第三階段:深入JVM核心——原理、診斷與優化

1、JVM簡介

JVM是Java Virtual Machine的簡稱,意為Java虛擬機器,使用軟體模擬Java 位元組碼的指令集。

2、JVM執行機制

JVM啟動流程

JVM基本結構

 

1.PC暫存器

每個執行緒擁有一個PC暫存器,線上程建立時建立,指向下一條指令的地址,執行本地方法時,PC的值為undefined。

2.方法區

儲存裝載的類資訊,包括型別的常量池、欄位、方法資訊、方法位元組碼等,通常和永久區(Perm)關聯在一起。JDK6時,String等常量資訊置於方法;JDK7時,已經移動到了堆。

3.Java堆

它和程式開發密切相關,應用系統物件都儲存在Java堆中,所有執行緒共享Java堆。對分代GC來說,堆也是分代的。它是GC的主要工作區間。

4.Java棧

執行緒私有,棧由一系列幀組成(因此Java棧也叫做幀棧),幀儲存一個方法的區域性變數、運算元棧、常量池指標,每一次方法呼叫建立一個幀,並壓棧。

// Java棧 – 區域性變量表 包含引數和區域性變數
public class StackDemo {

    public static int runStatic(int i,long l,float  f,Object o ,byte b){
        return 0;
    }


    public int runInstance(char c,short s,boolean b){
        return 0;
    }
}
// Java棧 – 函式呼叫組成幀棧
public static int runStatic(int i,long l,float  f,Object o ,byte b){
    return runStatic(i,l,f,o,b);
}
// Java棧 – 運算元棧  Java沒有暫存器,所有引數傳遞使用運算元棧
public static int add(int a,int b){
    int c=0;
    c=a+b;
    return c;
}

/*  
 0:   iconst_0 // 0壓棧
 1:   istore_2 // 彈出int,存放於區域性變數2
 2:   iload_0  // 把區域性變數0壓棧
 3:   iload_1 // 區域性變數1壓棧
 4:   iadd      //彈出2個變數,求和,結果壓棧
 5:   istore_2 //彈出結果,放於區域性變數2
 6:   iload_2  //區域性變數2壓棧
 7:   ireturn   //返回
*/

// Java棧 – 棧上分配
class BcmBasicString{
    public void method(){    
        BcmBasicString* str=new BcmBasicString;    ....    delete str;
        // 堆上分配,每次需要清理空間
    }
    public void method(){   
        // 棧上分配,函式呼叫完成自動清理 
        BcmBasicString str;  
          ....
    }
}


public class OnStackTest {
    public static void alloc(){
        byte[] b=new byte[2];
        b[0]=1;
    }
    public static void main(String[] args) {
        long b=System.currentTimeMillis();
        for(int i=0;i<100000000;i++){
            alloc();
        }
        long e=System.currentTimeMillis();
        System.out.println(e-b);
    }
}
// 使用-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC
// 輸出結果 5
// 使用-server -Xmx10m -Xms10m -XX:-DoEscapeAnalysis -XX:+PrintGC
// 輸出結果 ……
// [GC 3550K->478K(10240K), 0.0000977 secs]
// [GC 3550K->478K(10240K), 0.0001361 secs]
// [GC 3550K->478K(10240K), 0.0000963 secs]
// 564

小物件(一般幾十個bytes),在沒有逃逸的情況下,可以直接分配在棧上;直接分配在棧上,可以自動回收,減輕GC壓力;大物件或者逃逸物件無法棧上分配。

5.棧、堆、方法區互動

 

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);
    }
}

6.記憶體模型

每一個執行緒有一個工作記憶體和主存獨立,工作記憶體存放主存中變數的值的拷貝。當資料從主記憶體複製到工作儲存時,必須出現兩個動作:第一,由主記憶體執行的讀(read)操作;第二,由工作記憶體執行的相應的load操作;當資料從工作記憶體拷貝到主記憶體時,也出現兩個操作:第一個,由工作記憶體執行的儲存(store)操作;第二,由主記憶體執行的相應的寫(write)操作。每一個操作都是原子的,即執行期間不會被中斷。對於普通變數,一個執行緒中更新的值,不能馬上反應在其他變數中,如果需要在其他執行緒中立即可見,需要使用 volatile 關鍵字。

 

7.volatile

public class VolatileStopThread extends Thread{
    private volatile boolean stop = false;

    public void stopMe(){
        stop=true;
    }

    public void run(){
        int i=0;
        while(!stop){
            i++;
        }
        System.out.println("Stop thread");
    }

    public static void main(String args[]) throws InterruptedException{
        VolatileStopThread t=new VolatileStopThread();
        t.start();
        Thread.sleep(1000);
        t.stopMe();
        Thread.sleep(1000);
    }
}
// 沒有volatile的情況下,程式使用-server執行,將無法停止

 volatile不能代替鎖,一般認為volatile比鎖效能好(不絕對)。選擇使用volatile的條件是:語義是否滿足應用。

8.可見性

一個執行緒修改了變數,其他執行緒可以立即知道。保證可見性的方法:volatile;synchronized(unlock之前,寫變數值回主存);final(一旦初始化完成,其他執行緒就可見)。

9.有序性

在本執行緒內,操作都是有序的;線上程外觀察,操作都是無序的。(使用指令重排或主記憶體同步延時)

10.指令重排

執行緒內序列語義

破壞執行緒間的有序性 

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中,兩句話順序可能打亂。

class OrderExample {// 同步後保證有序性
    int a = 0;
    boolean flag = false;

    public synchronized void writer() {
        a = 1;                   
        flag = true;           
    }
    
    public synchronized void reader() {
        if (flag) {                
            int i =  a +1;      
            ……
        }
    }
}

指令重排基本規則

11.解釋執行

解釋執行以解釋方式執行位元組碼,意思是:讀一句執行一句 。

12.編譯執行(JIT)

將位元組碼編譯成機器碼,直接執行機器碼,執行時編譯,編譯後效能有數量級的提升,一般為10倍左右。

3、常用JVM配置引數

Trace跟蹤引數

-XX:+printGC 可以列印GC的簡要資訊

-XX:+PrintGCDetails 列印GC詳細資訊

-XX:+PrintGCTimeStamps 列印CG發生的時間戳

-Xloggc:log/filename.log 指定GC log的位置,以檔案輸出,幫助開發人員分析問題

-XX:+PrintHeapAtGC 每次一次GC後,都列印堆資訊

-XX:+TraceClassLoading 監控類的載入

-XX:+PrintClassHistogram 按下Ctrl+Break後,列印類的資訊,分別顯示:序號、例項數量、總大小、型別

 

堆的分配引數

-Xmx –Xms 指定最大堆和最小堆,示例:-Xmx20m -Xms5m 

-Xmn 設定新生代大小

-XX:NewRatio 新生代(eden+2*s)和老年代(不包含永久區)的比值,示例:-XX:NewRatio4,4 表示新生代:老年代=1:4,即年輕代佔堆的1/5

-XX:SurvivorRatio 設定兩個Survivor區和eden的比,示例:-XX:SurvivorRatio8,8表示 兩個Survivor :eden=2:8,即一個Survivor佔年輕代的1/10

-XX:+HeapDumpOnOutOfMemoryError OOM時匯出堆到檔案

-XX:+HeapDumpPath 匯出OOM的路徑

-XX:OnOutOfMemoryError 在OOM時,執行一個指令碼 "-XX:OnOutOfMemoryError=D:/tools/jdk1.7_40/bin/printstack.bat %p“  當程式OOM時,在D:/a.txt中將會生成執行緒的dump,可以在OOM時,傳送郵件,甚至是重啟程式

小結:根據實際事情調整新生代和倖存代的大小,官方推薦新生代佔堆的3/8,倖存代佔新生代的1/10。在OOM時,記得Dump出堆,確保可以排查現場問題。

永久區分配引數

-XX:PermSize  -XX:MaxPermSize 設定永久區的初始空間和最大空間,他們表示一個系統可以容納多少個型別

棧大小分配

-Xss 通常只有幾百K,決定了函式呼叫的深度,每個執行緒都有獨立的棧空間,區域性變數、引數分配在棧上

public class TestStackDeep {
	private static int count=0;
	public static void recursion(long a,long b,long c){
		long e=1,f=2,g=3,h=4,i=5,k=6,q=7,x=8,y=9,z=10;
		count++;
		recursion(a,b,c);
	}
	public static void main(String args[]){
		try{
			recursion(0L,0L,0L);
		}catch(Throwable e){
			System.out.println("deep of calling = "+count);
			e.printStackTrace();
		}
	}
}
/*
遞迴呼叫
-Xss128K
deep of calling = 701
java.lang.StackOverflowError

-Xss256K
deep of calling = 1817
java.lang.StackOverflowError
*/

4、GC演算法

GC的概念

Garbage Collection 垃圾收集,1960年 List 使用了GC,Java中,GC的物件是堆空間和永久區

GC演算法

1.引用計數法

老牌垃圾回收演算法,通過引用計算來回收垃圾。引用計數器的實現很簡單,對於一個物件A,只要有任何一個物件引用了A,則A的引用計數器就加1,當引用失效時,引用計數器就減1。只要物件A的引用計數器的值為0,則物件A就不可能再被使用。

引用計數法的問題是引用和去引用伴隨加法和減法,影響效能,並且很難處理迴圈引用。

 2.標記-清除法

標記-清除演算法是現代垃圾回收演算法的思想基礎。標記-清除演算法將垃圾回收分為兩個階段:標記階段和清除階段。一種可行的實現是,在標記階段,首先通過根節點,標記所有從根節點開始的可達物件。因此,未被標記的物件就是未被引用的垃圾物件。然後,在清除階段,清除所有未被標記的物件。

 3.標記-壓縮法

標記-壓縮演算法適合用於存活物件較多的場合,如老年代。它在標記-清除演算法的基礎上做了一些優化。和標記-清除演算法一樣,標記-壓縮演算法也首先需要從根節點開始,對所有可達物件做一次標記。但之後,它並不簡單的清理未標記的物件,而是將所有的存活物件壓縮到記憶體的一端。之後,清理邊界外所有的空間。

4.複製演算法

與標記-清除演算法相比,複製演算法是一種相對高效的回收方法。不適用於存活物件較多的場合,如老年代。將原有的記憶體空間分為兩塊,每次只使用其中一塊,在垃圾回收時,將正在使用的記憶體中的存活物件複製到未使用的記憶體塊中,之後,清除正在使用的記憶體塊中的所有物件,交換兩個記憶體的角色,完成垃圾回收 。

 

分代思想

依據物件的存活週期進行分類,短命物件歸為新生代,長命物件歸為老年代。根據不同代的特點,選取合適的收集演算法:少量物件存活,適合複製演算法;大量物件存活,適合標記清理或者標記壓縮。

可觸及性

可觸及的:從根節點可以觸及到這個物件 。

可復活的:一旦所有引用被釋放,就是可復活狀態,因為在finalize()中可能復活該物件 。

不可觸及的:在finalize()後,可能會進入不可觸及狀態。不可觸及的物件不可能復活,可以回收。

public class CanReliveObj {
	public static CanReliveObj 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";
	}
    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 可用");
    }
}
// 得到的結果如下
/*
CanReliveObj finalize called
obj 可用
第二次gc
obj 是 null
*/

經驗:避免使用finalize(),操作不慎可能導致錯誤。優先順序低,何時被呼叫,不確定,何時發生GC不確定。可以使用try-catch-finally來替代它。

Stop-The-World

Stop-The-World是Java中一種全域性暫停的現象。全域性停頓,所有Java程式碼停止,native程式碼可以執行,但不能和JVM互動。這種現象多半由於GC引起,如Dump執行緒、死鎖檢查、堆Dump等。

GC時為什麼會有全域性停頓?

類比在聚會時打掃房間,聚會時很亂,又有新的垃圾產生,房間永遠打掃不乾淨,只有讓大家停止活動了,才能將房間打掃乾淨。

STW的危害

長時間服務停止,沒有響應,遇到HA系統,可能引起主備切換,嚴重危害生產環境。

public static class PrintThread extends Thread{
	public static final long starttime=System.currentTimeMillis();
	@Override
	public void run(){
		try{
			while(true){
				long t=System.currentTimeMillis()-starttime;
				System.out.println("time:"+t);
				Thread.sleep(100);
			}
		}catch(Exception e){
			
		}
	}
}
public static class MyThread extends Thread{
	HashMap<Long,byte[]> map=new HashMap<Long,byte[]>();
	@Override
	public void run(){
		try{
			while(true){
				if(map.size()*512/1024/1024>=450){
					System.out.println(“=====準備清理=====:"+map.size());
					map.clear();
				}
				
				for(int i=0;i<1024;i++){
					map.put(System.nanoTime(), new byte[512]);
				}
				Thread.sleep(1);
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}
// 使用下列命令啟動執行緒
/*
-Xmx512M -Xms512M -XX:+UseSerialGC -Xloggc:gc.log -XX:+PrintGCDetails  -Xmn1m -XX:PretenureSizeThreshold=50 -XX:MaxTenuringThreshold=1
*/

 預期中每秒有10條輸出,但我們可以看到紅色標記部分存在著一定的停頓時間,這部分就是GC造成的STW現象。

5、GC引數

序列收集器

序列收集器是最古老,最穩定的收集器,效率高,但可能會產生較長的停頓。使用-XX:+UseSerialGC啟動序列收集器。新生代、老年代使用序列回收演算法,新生代使用複製演算法,而老年代使用標記-壓縮演算法。

並行收集器 ParNew

-XX:+UseParNewGC 新生代並行,老年代序列。Serial收集器新生代的並行版本,採用複製演算法,多執行緒任務,需要多核支援。可使用-XX:ParallelGCThreads 限制執行緒數量。

-XX:MaxGCPauseMills 最大停頓時間,單位毫秒,GC盡力保證回收時間不超過設定值

-XX:GCTimeRatio 0-100的取值範圍 垃圾收集時間佔總時間的比 預設99,即最大允許1%時間做GC

這兩個引數是矛盾的。因為停頓時間和吞吐量不可能同時調優 。

CMS收集器

Concurrent Mark Sweep 併發標記清除。標記-清除演算法與標記-壓縮相比,併發階段會降低吞吐量。老年代收集器(新生代使用ParNew)。-XX:+UseConcMarkSweepGC啟動CMS收集器。

CMS執行過程比較複雜,著重實現了標記的過程,可分為:

初始標記:根可以直接關聯到的物件,速度快

併發標記(和使用者執行緒一起):主要標記過程,標記全部物件

重新標記:由於併發標記時,使用者執行緒依然執行,因此在正式清理前,再做修正

併發清除(和使用者執行緒一起):基於標記結果,直接清理物件

特點:

儘可能降低停頓

會影響系統整體吞吐量和效能。比如,在使用者執行緒執行過程中,分一半CPU去做GC,系統性能在GC階段,反應速度就下降一半

清理不徹底。因為在清理階段,使用者執行緒還在執行,會產生新的垃圾,無法清理

因為和使用者執行緒一起執行,不能在空間快滿時再清理。-XX:CMSInitiatingOccupancyFraction設定觸發GC的閾值,如果不幸記憶體預留空間不夠,就會引起concurrent mode failure 。

-XX:+ UseCMSCompactAtFullCollection Full GC後,進行一次整理 整理過程是獨佔的,會引起停頓時間變長

-XX:+CMSFullGCsBeforeCompaction 設定進行幾次Full GC後,進行一次碎片整理

-XX:ParallelCMSThreads 設定CMS的執行緒數量

GC引數整理

-XX:+UseSerialGC:在新生代和老年代使用序列收集器

-XX:SurvivorRatio:設定eden區大小和survivior區大小的比例

-XX:NewRatio:新生代和老年代的比

-XX:+UseParNewGC:在新生代使用並行收集器

-XX:+UseParallelGC :新生代使用並行回收收集器

-XX:+UseParallelOldGC:老年代使用並行回收收集器

-XX:ParallelGCThreads:設定用於垃圾回收的執行緒數

-XX:+UseConcMarkSweepGC:新生代使用並行收集器,老年代使用CMS+序列收集器

-XX:ParallelCMSThreads:設定CMS的執行緒數量

-XX:CMSInitiatingOccupancyFraction:設定CMS收集器在老年代空間被使用多少後觸發

-XX:+UseCMSCompactAtFullCollection:設定CMS收集器在完成垃圾收集後是否要進行一次記憶體碎片的整理

-XX:CMSFullGCsBeforeCompaction:設定進行多少次CMS垃圾回收後,進行一次記憶體壓縮

-XX:+CMSClassUnloadingEnabled:允許對類元資料進行回收

-XX:CMSInitiatingPermOccupancyFraction:當永久區佔用率達到這一百分比時,啟動CMS回收

-XX:UseCMSInitiatingOccupancyOnly:表示只在到達閥值的時候,才進行CMS回收

使用jmeter監控效能

 6、類裝載器

class裝載驗證流程

載入

裝載類的第一個階段。取得類的二進位制流,轉為方法區資料結構,在Java堆中生成對應的java.lang.Class物件。

連結 -> 驗證

目的是為了保證Class流的格式是正確的。檔案格式的驗證:是否以0xCAFEBABE開頭、版本號是否合理;元資料驗證:是否有父類、繼承了final類?、非抽象類實現了所有的抽象方法;位元組碼驗證 (很複雜):執行檢查、棧資料型別和操作碼資料引數吻合、跳轉指令指定到合理的位置;符號引用驗證:常量池中描述類是否存在、訪問的方法或欄位是否存在且有足夠的許可權。

連結 -> 準備

分配記憶體,併為類設定初始值 (方法區中)。public static int v=1; 在準備階段中,v會被設定為0,在初始化的<clinit>中才會被設定為1,對於static final型別,在準備階段就會被賦上正確的值 public static final  int v=1; 

連結 -> 解析

符號引用(字串引用物件不一定被載入)替換為直接引用(指標或者地址偏移量引用物件一定在記憶體)

初始化

執行類構造器<clinit>,子類的<clinit>呼叫前保證父類的<clinit>被呼叫,<clinit>是執行緒安全的

什麼是類裝載器ClassLoader

ClassLoader是一個抽象類

ClassLoader的例項將讀入Java位元組碼將類裝載到JVM中

ClassLoader可以定製,滿足不同的位元組碼流獲取方式

ClassLoader負責類裝載過程中的載入階段

ClassLoader的重要方法:

 - public Class<?> loadClass(String name) throws ClassNotFoundException 載入並返回一個Class

 - protected final Class<?> defineClass(byte[] b, int off, int len) 定義一個類,不公開呼叫

 - protected Class<?> findClass(String name) throws ClassNotFoundException loadClass回撥該方法,自定義ClassLoader的推薦做法

 - protected final Class<?> findLoadedClass(String name) 尋找已經載入的類

JDK中ClassLoader預設設計模式 – 協同工作

 在查詢類的時候,先在底層的Loader查詢,是從下往上的。Apploader能找到,就不會去上層載入器載入。

使用-Xbootclasspath/a:D:/tmp/clz設定搜尋路徑。

雙親模式的問題

頂層ClassLoader,無法載入底層ClassLoader的類

解決:Thread. setContextClassLoader() 上下文載入器,是一個角色,用以解決頂層ClassLoader無法訪問底層ClassLoader的類的問題。基本思想是:在頂層ClassLoader中,傳入底層ClassLoader的例項。

static private Class getProviderClass(String className, ClassLoader cl,
        boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException
{
    try {
        if (cl == null) {
            if (useBSClsLoader) {
                return Class.forName(className, true, FactoryFinder.class.getClassLoader());
            } else {
                cl = ss.getContextClassLoader();
                if (cl == null) {
                    throw new ClassNotFoundException();
                }
                else {
                    return cl.loadClass(className); //使用上下文ClassLoader
                }
            }
        }
        else {
            return cl.loadClass(className);
        }
    }
    catch (ClassNotFoundException e1) {
        if (doFallback) {
            // Use current class loader - should always be bootstrap CL
            return Class.forName(className, true, FactoryFinder.class.getClassLoader());
        }
......
/*
程式碼來自於
javax.xml.parsers.FactoryFinder
展示如何在啟動類載入器載入AppLoader的類
上下文ClassLoader可以突破雙親模式的侷限性
*/

雙親模式是預設的模式,但不是必須這麼做。Tomcat的WebappClassLoader 就會先載入自己的Class,找不到再委託parent。OSGi的ClassLoader形成網狀結構,根據需要自由載入Class。

破壞雙親模式例子:先從底層ClassLoader載入

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    // First, check if the class has already been loaded
    Class re=findClass(name);
    if(re==null){
        System.out.println(“無法載入類:”+name+“ 需要請求父載入器");
        return super.loadClass(name,resolve);
    }
    return re;
}
protected Class<?> findClass(String className) throws ClassNotFoundException {
    Class clazz = this.findLoadedClass(className);
    if (null == clazz) {
        try {
            String classFile = getClassFile(className);
            FileInputStream fis = new FileInputStream(classFile);
            FileChannel fileC = fis.getChannel();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            WritableByteChannel outC = Channels.newChannel(baos);
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
             省略部分程式碼
            fis.close();
            byte[] bytes = baos.toByteArray();

            clazz = defineClass(className, bytes, 0, bytes.length);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return clazz;
}

熱替換

當一個class被替換後,系統無需重啟,替換的類立即生效。

7、效能監控工具

linux系統

uptime:系統時間 執行時間 例子中為7分鐘 連線數 每一個終端算一個連線 1,5,15分鐘內的系統平均負載 執行佇列中的平均程序數

top:可以知道哪個程式佔CPU最多 

 vmstat:可以統計系統的CPU,記憶體,swap,io等情況

pidstat:可以細緻的觀察程序,監控CPU、監控IO、監控記憶體等等。

windows系統

 工作管理員

perfmon:Windows自帶多功能效能監控工具,在命令列下輸入perfmon啟動。

Process Explorer:需要下載,能顯示程序間的關係等等。

pslist:命令列工具,可用於自動化資料收集,顯示java程式的執行情況。

java自帶的工具

jps:列出java程序,類似於ps命令。引數-q可以指定jps只輸出程序ID,不輸出類的短名稱;引數-m可以用於輸出傳遞給Java程序(主函式)的引數;引數-l可以用於輸出主函式的完整路徑;引數-v可以顯示傳遞給JVM的引數。

jinfo:可以用來檢視正在執行的Java應用程式的擴充套件引數,甚至支援在執行時,修改部分引數;-flag <name>:列印指定JVM的引數值;-flag [+|-]<name>:設定指定JVM引數的布林值;-flag <name>=<value>:設定指定JVM引數的值。

jmap:生成Java應用程式的堆快照和物件的統計資訊

jstack:列印執行緒dump。-l 列印鎖資訊;-m 列印java和native的幀資訊;-F 強制dump,當jstack沒有響應時使用

JConsole:圖形化監控工具,可以檢視Java應用程式的執行概況,監控堆資訊、永久區使用情況、類載入情況等

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

實戰分析——程式卡死

 

 

 

 

實戰分析——死鎖 

 

 

 8、java堆分析

記憶體溢位(OOM)的原因

1.佔用大量堆空間,直接溢位

解決方法:增大堆空間,及時釋放記憶體

2.永久區溢位

解決方法: 增大Perm區,允許Class回收

3.Java棧溢位:這裡的棧溢位指,在建立執行緒的時候,需要為執行緒分配棧空間,這個棧空間是向作業系統請求的,如果作業系統無法給出足夠的空間,就會丟擲OOM

解決方法: 減少堆記憶體,減少執行緒棧大小

4.直接記憶體溢位:ByteBuffer.allocateDirect()無法從作業系統獲得足夠的空間

解決方法: 減少堆記憶體,有意觸發GC

MAT(Memory Analyzer)使用基礎

 

它的功能十分強大,不再一一列舉 。下面介紹一下上面說到的淺堆和深堆。

淺堆:一個物件結構所佔用的記憶體大小。3個int型別以及一個引用型別合計佔用記憶體3*4+4=16個位元組,再加上物件頭的8個位元組,因此String物件佔用的空間,即淺堆的大小是16+8=24位元組。物件大小按照8位元組對齊。淺堆大小和物件的內容無關,只和物件的結構有關。

深堆:一個物件被GC回收後,可以真實釋放的記憶體大小,只能通過物件訪問到的(直接或者間接)所有物件的淺堆之和 (支配樹)。

9、Class檔案結構

access flag

Flag Name

Value

Interpretation

ACC_PUBLIC

0x0001

public

ACC_FINAL

0x0010

final,不能被繼承.

ACC_SUPER

0x0020

是否允許使用invokespecial指令,JDK1.2後,該值為true

ACC_INTERFACE

0x0200

是否是介面

ACC_ABSTRACT

0x0400

抽象類

ACC_SYNTHETIC

0x1000

該類不是由使用者程式碼生成,執行時生成的,沒有原始碼

ACC_ANNOTATION

0x2000

是否為註解

ACC_ENUM

0x4000

是否是列舉

Flag Name

Value

Interpretation

ACC_PUBLIC

0x0001

public

ACC_PRIVATE

0x0002

private

ACC_PROTECTED

0x0004

protected

ACC_STATIC

0x0008

static

ACC_FINAL

0x0010

final

ACC_SYNCHRONIZED

0x0020

synchronized

ACC_BRIDGE

0x0040

編譯器產生 橋接方法

ACC_VARARGS

0x0080

可變引數

ACC_NATIVE

0x0100

native

ACC_ABSTRACT

0x0400

abstract

ACC_STRICT

0x0800

strictfp

ACC_SYNTHETIC

0x1000

不在原始碼中,由編譯器產生

 attribute

名稱

使用者

描述

Deprecated

field method

欄位、方法、類被廢棄

ConstantValue

field

final常量

Code

method

方法的位元組碼和其他資料

Exceptions

method

方法的異常

LineNumberTable

Code_Attribute

方法行號和位元組碼對映

LocalVaribleTable

Code_Attribute

方法區域性變量表描述

SourceFile

Class file

原始檔名

Synthetic

field method

編譯器產生的方法或欄位

 class檔案結構例子

 

 

10、JVM位元組碼執行

javap

class檔案反彙編工具 

簡單位元組碼執行示例

 

 

 

 

 

常用位元組碼

1.常量入棧

aconst_null null物件入棧

iconst_m1   int常量-1入棧

iconst_0      int常量0入棧

iconst_5

lconst_1      long常量1入棧

fconst_1      float 1.0入棧

dconst_1     double 1.0 入棧

bipush        8位帶符號整數入棧

sipush         16位帶符號整數入棧

ldc               常量池中的項入棧

2.區域性變數壓棧

xload(x為i l f d a) 分別表示int,long,float,double,object ref

xload_n(n為0 1 2 3)

xaload(x為i l f d a b c s) 分別表示int, long, float, double, obj ref ,byte,char,short,從陣列中取得給定索引的值,將該值壓棧 iaload 執行前,棧:..., arrayref, index 它取得arrayref所在陣列的index的值,並將值壓棧 執行後,棧:..., value

3.出棧裝載入區域性變數

xstore(x為i l f d a) 出棧,存入區域性變數

xstore_n(n 0 1 2 3) 出棧,將值存入第n個區域性變數

xastore(x為i l f d a b c s) 將值存入陣列中 iastore:執行前,棧:...,arrayref, index, value;執行後,棧:... ;將value存入arrayref[index]

4.通用棧操作(無型別)

nop

pop 彈出棧頂1個字長

dup 複製棧頂1個字長,複製內容壓入棧

5.型別轉換

i2l i2f l2i l2f l2d f2i f2d d2i d2l d2f i2b i2c i2s等(i2l:將int轉為long;執行前,棧:..., value;執行後,棧:...,result.word1,result.word2;彈出int,擴充套件為long,併入棧)

6.整數運算

iadd ladd isub lsub idiv ldiv imul lmul iinc等

7.浮點運算

fadd dadd fsub dsub fdiv ddiv fmul dmul等

8.物件操作指令

new getfield putfield getstatic putstatic

9.條件控制

ifeq  如果為0,則跳轉 (ifeq 引數 byte1,byte2 value出棧 ,如果棧頂value為0則跳轉到(byte1<<8)|byte2 執行前,棧:...,value 執行後,棧:...)

ifne  如果不為0,則跳轉

iflt   如果小於0 ,則跳轉

ifge  如果大於0,則跳轉

if_icmpeq 如果兩個int相同,則跳轉

10.方法呼叫

invokevirtual invokespecial invokestatic invokeinterface xreturn(x為 i l f d a 或為空)

ASM

Java位元組碼操作框架,可以用於修改現有類或者動態產生新類

JIT

位元組碼執行效能較差,所以可以對於熱點程式碼編譯成機器碼再執行,在執行時的編譯, 叫做JIT Just-In-Time。基本思路是,將熱點程式碼,就是執行比較頻繁的程式碼(熱點程式碼 hot spot code),編譯成機器碼。

public class JITTest {

    public static void met(){
        int a=0,b=0;
        b=a+b;
    }
    
    public static void main(String[] args) {
        for(int i=0;i<1000;i++){
            met();
        }
    }
}
// 使用下列引數啟動測試
/*
-XX:CompileThreshold=1000
-XX:+PrintCompilation
*/
// 輸出如下
/*
56    1             java.lang.String::hashCode (55 bytes)
56    2             java.lang.String::equals (81 bytes)
57    3             java.lang.String::indexOf (70 bytes)
60    4             java.lang.String::charAt (29 bytes)
61    5             java.lang.String::length (6 bytes)
61    6             java.lang.String::lastIndexOf (52 bytes)
61    7             java.lang.String::toLowerCase (472 bytes)
67    8             geym.jvm.ch2.jit.JITTest::met (9 bytes)
*/

 相關引數

-Xint 解釋執行

-Xcomp 全部編譯執行

-Xmixed 預設,混合

掃描關注我們的各種:落餅楓林,與我們一起進步~~