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 下查找,找到了
總結: