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

总结:
在这里插入图片描述

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