JVM學習筆記(二)(類加載與字節碼技術),看這篇文章就夠了

1_類文件結構
在這裏插入圖片描述
一個簡單的 HelloWorld.java
在這裏插入圖片描述
執行 javac -parameters -d . HellowWorld.java
編譯爲 HelloWorld.class 後是這個樣子的:
在這裏插入圖片描述
根據 JVM 規範,類文件結構如下:
在這裏插入圖片描述
01_1 魔數
在這裏插入圖片描述
01_2 版本
在這裏插入圖片描述
01_3 常量池
在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
01_4 訪問標識和繼承信息
在這裏插入圖片描述在這裏插入圖片描述
01_5 Field信息
在這裏插入圖片描述01_6 Method信息
在這裏插入圖片描述

2_ 字節碼指令
01_ javap 工具
自己分析類文件結構太麻煩了,Oracle 提供了 javap 工具來反編譯 class 文件:javap -v name.class

02_ 圖解方法的執行流程
1)原始 java 代碼

/**
 * 演示 字節碼指令 和 操作數棧、常量池的關係
 */
public class Demo3_1 {
    public static void main(String[] args) {
        int a = 10;
        int b = Short.MAX_VALUE + 1;
        int c = a + b;
        System.out.println(c);
    }
}

2)編譯後的字節碼文件
在這裏插入圖片描述
3)常量池載入運行時常量池
在這裏插入圖片描述
4)方法字節碼載入方法區
在這裏插入圖片描述
5)main 線程開始運行,分配棧幀內存
(stack=2,locals=4)=>(表示棧的高度爲2,局部變量表的長度大小爲4)
在這裏插入圖片描述
6)執行引擎開始執行字節碼
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述
istore_2
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
iload_2在這裏插入圖片描述
在這裏插入圖片描述
istore_3
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
2.4 練習 - 分析 i++
目的:從字節碼角度分析 a++ 相關題目
源碼:

package cn.itcast.jvm.t3.bytecode;

/**
 * 從字節碼角度分析 a++  相關題目
 */
public class Demo3_2 {
    public static void main(String[] args) {
        int a = 10;
        int b = a++ + ++a + a--;
        System.out.println(a);
        System.out.println(b);
    }
}

字節碼:在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述2.5 條件判斷指令在這裏插入圖片描述在這裏插入圖片描述字節碼:

	1: istore_1 
	2: iload_1 
	3: ifne 12 
	6: bipush 10
	8: istore_1
	9: goto 15 
	12: bipush 20 
	14: istore_1 
	15: return

以上比較指令中沒有 long,float,double 的比較,那麼它們要比較怎麼辦?參考 https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.lcmp

2.6 循環控制指令(for循環和while循環一樣)
在這裏插入圖片描述
2.7 練習 - 判斷結果
在這裏插入圖片描述
2.8 構造方法
在這裏插入圖片描述
2) ()V

package cn.itcast.jvm.t3.bytecode;
public class Demo3_8_2 {

    private String a = "s1";

    {
        b = 20;
    }

    private int b = 10;

    {
        a = "s2";
    }

    public Demo3_8_2(String a, int b) {
        this.a = a;
        this.b = b;
    }

    public static void main(String[] args) {
        Demo3_8_2 d = new Demo3_8_2("s3", 30);
        System.out.println(d.a);
        System.out.println(d.b);
    }
}

編譯器會按從上至下的順序,收集所有 {} 代碼塊成員變量賦值的代碼,形成新的構造方法,但原始構造方法內的代碼總是在最後
在這裏插入圖片描述
2.9 方法調用
看一下幾種不同的方法調用對應的字節碼指令:

package cn.itcast.jvm.t3.bytecode;

public class Demo3_9 {
    public Demo3_9() { }

    private void test1() { }

    private final void test2() { }

    public void test3() { }

    public static void test4() { }

    @Override
    public String toString() {
        return super.toString();
    }

    public static void main(String[] args) {
        Demo3_9 d = new Demo3_9();
        d.test1();
        d.test2();
        d.test3();
        d.test4();
        Demo3_9.test4();
        d.toString();
    }
}
```![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20200518202153189.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3OTg5NzM4,size_16,color_FFFFFF,t_70)
2.10 多態的原理

```java
package cn.itcast.jvm.t3.bytecode;
import java.io.IOException;

/**
 * 演示多態原理,注意加上下面的 JVM 參數,禁用指針壓縮
 * -XX:-UseCompressedOops -XX:-UseCompressedClassPointers
 */
public class Demo3_10 {

    public static void test(Animal animal) {
        animal.eat();
        System.out.println(animal.toString());
    }

    public static void main(String[] args) throws IOException {
        test(new Cat());
        test(new Dog());
        System.in.read();
    }
}

abstract class Animal {
    public abstract void eat();

    @Override
    public String toString() {
        return "我是" + this.getClass().getSimpleName();
    }
}

class Dog extends Animal {

    @Override
    public void eat() {
        System.out.println("啃骨頭");
    }
}

class Cat extends Animal {

    @Override
    public void eat() {
        System.out.println("吃魚");
    }
}

在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述2.11 異常處理
try-catch

package cn.itcast.jvm.t3.bytecode;

public class Demo3_11_1 {

    public static void main(String[] args) {
        int i = 0;
        try {
            i = 10;
            
        } catch (Exception e) {
            i = 20;
        }
    }
}

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
javac -g Demo3_11_2.java,
javap -v Demo3_11_2.class

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: bipush        10
         4: istore_1
         5: goto          26
         8: astore_2
         9: bipush        30
        11: istore_1
        12: goto          26
        15: astore_2
        16: bipush        40
        18: istore_1
        19: goto          26
        22: astore_2
        23: bipush        50
        25: istore_1
        26: return
      Exception table:
         from    to  target type
             2     5     8   Class java/lang/ArithmeticException
             2     5    15   Class java/lang/NullPointerException
             2     5    22   Class java/lang/Exception
      LineNumberTable:
        line 6: 0
        line 8: 2
        line 15: 5
        line 9: 8
        line 10: 9
        line 15: 12
        line 11: 15
        line 12: 16
        line 15: 19
        line 13: 22
        line 14: 23
        line 16: 26
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            9       3     2     e   Ljava/lang/ArithmeticException;
           16       3     2     e   Ljava/lang/NullPointerException;
           23       3     2     e   Ljava/lang/Exception;
            0      27     0  args   [Ljava/lang/String;
            2      25     1     i   I

● 因爲異常出現時,只能進入 Exception table 中一個分支,所以局部變量表 slot 2 位置被共用

multi-catch 的情況

public class Demo3_11_3 {

    public static void main(String[] args) {
        try {
            Method test = Demo3_11_3.class.getMethod("test");
            test.invoke(null);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    public static void test() {
        System.out.println("ok");
    }
}

字節碼:

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: ldc           #2                  // class cn/itcast/jvm/t3/bytecode/Demo3_11_3
         2: ldc           #3                  // String test
         4: iconst_0
         5: anewarray     #4                  // class java/lang/Class
         8: invokevirtual #5                  // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
        11: astore_1
        12: aload_1
        13: aconst_null
        14: iconst_0
        15: anewarray     #6                  // class java/lang/Object
        18: invokevirtual #7                  // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
        21: pop
        22: goto          30
        25: astore_1
        26: aload_1
        27: invokevirtual #11                 // Method java/lang/ReflectiveOperationException.printStackTrace:()V
        30: return
      Exception table:
         from    to  target type
             0    22    25   Class java/lang/NoSuchMethodException
             0    22    25   Class java/lang/IllegalAccessException
             0    22    25   Class java/lang/reflect/InvocationTargetException
      LineNumberTable:
        line 10: 0
        line 11: 12
        line 14: 22
        line 12: 25
        line 13: 26
        line 15: 30
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           12      10     1  test   Ljava/lang/reflect/Method;
           26       4     1     e   Ljava/lang/ReflectiveOperationException;
            0      31     0  args   [Ljava/lang/String;

finally

package cn.itcast.jvm.t3.bytecode;

public class Demo3_11_4 {

    public static void main(String[] args) {
        int i = 0;
        try {
            i = 10;
        } catch (Exception e) {
            i = 20;
        } finally {
            i = 30;
        }
    }
}
 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=4, args_size=1
         0: iconst_0
         1: istore_1
         2: bipush        10
         4: istore_1
         5: bipush        30
         7: istore_1
         8: goto          27
        11: astore_2
        12: bipush        20
        14: istore_1
        15: bipush        30
        17: istore_1
        18: goto          27
        21: astore_3
        22: bipush        30
        24: istore_1
        25: aload_3
        26: athrow
        27: return
      Exception table:
         from    to  target type
             2     5    11   Class java/lang/Exception
             2     5    21   any
            11    15    21   any
      LineNumberTable:
        line 6: 0
        line 8: 2
        line 12: 5
        line 13: 8
        line 9: 11
        line 10: 12
        line 12: 15
        line 13: 18
        line 12: 21
        line 14: 27
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           12       3     2     e   Ljava/lang/Exception;
            0      28     0  args   [Ljava/lang/String;
            2      26     1     i   I

可以看到 finally 中的代碼被複制了 3 份,分別放入 try 流程,catch 流程以及 catch 剩餘的異常類型流程,保證finally裏面的內容最後執行。

2.12 練習 - finally 面試題

finally 出現了 return
先問問自己,下面的題目輸出什麼?

public class Demo3_12_2 {
    public static void main(String[] args) {
        int result = test();
        System.out.println(result);
    }

    public static int test() {
   
        try {
            return 10;
        } finally {
            return 20;
        }
    }
}
 public static int test();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=0
         0: bipush        10
         2: istore_0
         3: bipush        20
         5: ireturn
         6: astore_1
         7: bipush        20
         9: ireturn
      Exception table:
         from    to  target type
             0     3     6   any
      LineNumberTable:
        line 12: 0
        line 14: 3

finally 對返回值影響
同樣問問自己,下面的題目輸出什麼?

public class Demo3_12_2 {
    public static void main(String[] args) {
        int result = test();
        System.out.println(result);
    }

    public static int test() {
        int i = 10;
        try {
            return i;
        } finally {
            i = 20;
        }
    }
}
 public static int test();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=3, args_size=0
         0: bipush        10   //10放入棧頂
         2: istore_0     // 10 -> i
         3: iload_0      // <- i(10)
         4: istore_1     // 10 -> slot 1,暫存至 slot 1,目的是爲了固定返回值
         5: bipush        20   // <- 20 放入棧頂
         7: istore_0    // 20 -> i
         8: iload_1     // <- slot 1(10) 載入 slot 1 暫存的值
         9: ireturn         // 返回棧頂的 int(10)
        10: astore_2
        11: bipush        20
        13: istore_0
        14: aload_2
        15: athrow
      Exception table:
         from    to  target type
             3     5    10   any
      LineNumberTable:
        line 10: 0
        line 12: 3
        line 14: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            3      13     0     i   I

2.13 synchronized

public class Demo3_13 {

    public static void main(String[] args) {
        Object lock = new Object();
        synchronized (lock) {
            System.out.println("ok");
        }
    }
}
 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: new           #2                  // class java/lang/Object
         3: dup
         4: invokespecial #1                  // Method java/lang/Object."<init>":()V
         7: astore_1                         // lock引用 -> lock**
         8: aload_1							// <- lock (synchronized開始)
         9: dup
        10: astore_2						// lock引用 -> slot 2
        11: monitorenter
        12: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        15: ldc           #4                  // String ok
        17: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        20: aload_2							// <- slot 2(lock引用)
        21: monitorexit						// monitorexit(lock引用)
        22: goto          30
        25: astore_3						// any -> slot 3
        26: aload_2							// <- slot 2(lock引用)
        27: monitorexit						// monitorexit(lock引用)
        28: aload_3
        29: athrow
        30: return
      Exception table:
         from    to  target type
            12    22    25   any
            25    28    25   any
      LineNumberTable:
        line 6: 0
        line 7: 8
        line 8: 12
        line 9: 20
        line 10: 30
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      31     0  args   [Ljava/lang/String;
            8      23     1  lock   Ljava/lang/Object;

在這裏插入圖片描述
3. 編譯期處理
在這裏插入圖片描述
3.1 默認構造器
在這裏插入圖片描述
3.2 自動拆裝箱
在這裏插入圖片描述
顯然之前版本的代碼太麻煩了,需要在基本類型和包裝類型之間來回轉換(尤其是集合類中操作的都是包裝類型),因此這些轉換的事情在 JDK 5 以後都由編譯器在編譯階段完成。即 代碼片段1 都會在編譯階段被轉換爲 代碼片段2。

3.3 泛型集合取值
在這裏插入圖片描述
擦除的是字節碼上的泛型信息,可以看到 LocalVariableTypeTable 仍然保留了方法參數泛型的信息
在這裏插入圖片描述
3.4 可變參數
在這裏插入圖片描述3.5 foreach 循環

package cn.itcast.jvm.t3.candy;

public class Candy5_1 {
    public static void main(String[] args) {
        int[] array = {1, 2, 3, 4, 5}; // 數組賦初值的簡化寫法也是語法糖哦
        for (int e : array) {
            System.out.println(e);
        }
    }
}

在這裏插入圖片描述
而集合的循環:

package cn.itcast.jvm.t3.candy;

import java.util.Arrays;
import java.util.List;

public class Candy5_2 {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        for (Integer i : list) {
            System.out.println(i);
        }
    }
}

在這裏插入圖片描述
4. 類加載階段
4.1 加載
在這裏插入圖片描述
在這裏插入圖片描述
4.2 鏈接
驗證
驗證類是否符合 JVM規範,安全性檢查。用 UE 等支持二進制的編輯器修改 HelloWorld.class 的魔數,在控制檯運行
在這裏插入圖片描述
準備
在這裏插入圖片描述
解析

將常量池中的符號引用解析爲直接引用

package cn.itcast.jvm.t3.load;
import java.io.IOException;
/**
 * 解析的含義
 */
public class Load2 {
    public static void main(String[] args) throws ClassNotFoundException, IOException {
          ClassLoader classloader = Load2.class.getClassLoader();
          // loadClass 方法不會導致類的解析和初始化
          Class<?> c = classloader.loadClass("cn.itcast.jvm.t3.load.C");
//        new C();
        System.in.read();
    }
}

class C {
    D d = new D();
}

class D {
}

4.3 初始化
在這裏插入圖片描述
   ● loadClass 方法
   ● Class.forName 的參數 2 爲 false 時

驗證(實驗時請先全部註釋,每次只執行其中一個)

package cn.itcast.jvm.t3.load;

import java.io.IOException;

public class Load3 {
    static {
        System.out.println("main init");
    }
    public static void main(String[] args) throws ClassNotFoundException, IOException {
//        // 1. 靜態常量不會觸發初始化
//        System.out.println(B.b);
//        // 2. 類對象.class 不會觸發初始化
//        System.out.println(B.class);
//        // 3. 創建該類的數組不會觸發初始化
//        System.out.println(new B[0]);
        // 4. 不會初始化類 B,但會加載 B、A
//        ClassLoader cl = Thread.currentThread().getContextClassLoader();
//        cl.loadClass("cn.itcast.jvm.t3.load.B");
//        // 5. 不會初始化類 B,但會加載 B、A

//        ClassLoader c2 = Thread.currentThread().getContextClassLoader();
//        Class.forName("cn.itcast.jvm.t3.load.B", false, c2);
        System.in.read();


//        // 1. 首次訪問這個類的靜態變量或靜態方法時
//        System.out.println(A.a);
//        // 2. 子類初始化,如果父類還沒初始化,會引發
//        System.out.println(B.c);
//        // 3. 子類訪問父類靜態變量,只觸發父類初始化
        System.out.println(B.a);
//        // 4. 會初始化類 B,並先初始化類 A
//        Class.forName("cn.itcast.jvm.t3.load.B");


    }
}

class A {
    static int a = 0;
    static {
        System.out.println("a init");
    }
}

class B extends A {
    final static double b = 5.0;
    static boolean c = false;
    static {
        System.out.println("b init");
    }
}

4.4 練習
從字節碼分析,使用 a,b,c 這三個常量是否會導致 E 初始化

package cn.itcast.jvm.t3.load;

public class Load4 {
    public static void main(String[] args) {
        System.out.println(E.a);
        System.out.println(E.b);
        System.out.println(E.c);

    }
}

class E {
    public static final int a = 10;
    public static final String b = "hello";
    public static final Integer c = 20;  // Integer.valueOf(20)
    static {
        System.out.println("init E");
    }
}

典型應用 - 完成懶惰初始化單例模式

package cn.itcast.jvm.t3.load;

public class Load9 {
    public static void main(String[] args) {
//        Singleton.test();
        Singleton.getInstance();
    }

}

class Singleton {

    public static void test() {
        System.out.println("test");
    }

    private Singleton() {}

    private static class LazyHolder{
        private static final Singleton SINGLETON = new Singleton();
        static {
            System.out.println("lazy holder init");
        }
    }

    public static Singleton getInstance() {
        return LazyHolder.SINGLETON;
    }
}

在這裏插入圖片描述
5. 類加載器
以 JDK 8 爲例:
在這裏插入圖片描述
5.1 啓動類加載器
用 Bootstrap 類加載器加載類:

package cn.itcast.jvm.t3.load;

public class F {
    static {
        System.out.println("bootstrap F init");
    }
}

執行

package cn.itcast.jvm.t3.load;

public class Load5_1 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> aClass = Class.forName("cn.itcast.jvm.t3.load.F");
        System.out.println(aClass.getClassLoader()); // AppClassLoader  ExtClassLoader
    }
}

輸出
在這裏插入圖片描述
在這裏插入圖片描述
5.2 擴展類加載器
在這裏插入圖片描述在這裏插入圖片描述
5.3 雙親委派模式
所謂的雙親委派,就是指調用類加載器的 loadClass 方法時,查找類的規則
在這裏插入圖片描述
1 sun.misc.Launcher-AppClassLoader //1 處, 開始查看已加載的類,結果沒有
2 sun.misc.Launcher-AppClassLoader // 2 處,委派上級sun.misc.Launcher-ExtClassLoader.loadClass()
3. sun.misc.Launcher–ExtClassLoader // 1 處,查看已加載的類,結果沒有
4.sun.misc.Launcher-ExtClassLoader // 3 處,沒有上級了,則委派BootstrapClassLoader查找
5. BootstrapClassLoader 是在 JAVA_HOME/jre/lib 下找 H 這個類,顯然沒有
6. sun.misc.Launcher-ExtClassLoader // 4 處,調用自己的 findClass 方法,是在
JAVA_HOME/jre/lib/ext 下找 H 這個類,顯然沒有,回到sun.misc.Launcher-AppClassLoader 的 // 2 處
7. 繼續執行到 sun.misc.Launcher-AppClassLoader // 4 處,調用它自己的 findClass 方法,在classpath 下查找,找到了

總結:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章