类加载与字节码

1 类加载器

层级关系由上到下:

  • 启动类加载器(Bootstrap ClassLoader) 获取时打印null
  • 拓展类加载器(Extension ClassLoader) 获取时打印ExtClassLoader
  • 应用类加载器(Application ClassLoader) 获取时打印AppClassLoader
  • 自定义类加载器

1.1 双亲委派机制

类加载的流程:

  1. 检查该类是否已经加载,来避免重复加载
  2. 当一个类加载器收到类加载任务时,将加载任务传递给上层类加载器,最终传递到顶层BootstrapClassLoader,只有上层的类加载器无法完成加载任务时,才会执行加载任务。
  3. 通过双亲委派机制,保证了无论采用哪个类加载器加载具体的类,最终都是传递给相同的类加载器去加载,保证了最终得到的对象都是同一个。同时也避免了重复加载的问题。
protected Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 1.检查该类是否已经加载
        Class c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    //2. 有上级的话,委派上级 loadClass
                    c = parent.loadClass(name, false);
                } else {
                    // 3. 如果没有上级(ExtClassLoader),则委派BootstrapClassLoader
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                //当前的类加载器无法找到当前的类,会抛出异常,捕获异常后不做任何处理,继续用下一层类加载器尝试加载
            }

            if (c == null) {
                long t1 = System.nanoTime();
                // 4. 每一层找不到,调用 findClass 方法(每个类加载器自己拓展)来加载
                c = findClass(name);

                // 5. 记录耗时
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

1.2 自定义类加载器

使用场景:

  • 想加载非 classpath 随意路径中的类文件
  • 都是通过接口来使用实现,希望解耦时,常用在框架设计
  • 这些类希望予以隔离,不同应用的同名类都可以加载,不冲突,常见于 tomcat 容器

实现步骤:

  1. 继承 ClassLoader 父类
  2. 要遵从双亲委派机制,重写 findClass 方法
  • 注意不是重写 loadClass 方法,否则不会走双亲委派机制
  1. 读取类文件的字节码
  2. 调用父类的 defifineClass 方法来加载类
  3. 使用者调用该类加载器的 loadClass 方法
public class MyClassLoader extends ClassLoader {
    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        String path = "d:\\project\\" + name + ".class";
        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            Files.copy(Paths.get(path), os);

            byte[] bytes = os.toByteArray();
            return defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException("类文件未找到",e);
        }
    }
}

ps:判断类是否相同需要判断包名、类名、类加载器是否相同,有一个不同则会判定不是同一个类。

2 类加载和字节码技术

类文件结构:

名称 类型(所占字节) 数量
magic(魔数) u4 1
minor_version(次版本号) u2 1
major_version(主版本号) u2 1
constant_pool_count(常量池数量) u2 1
constant_pool(常量池) cp_info constant_pool_count-1
access_flags(类或接口访问标志) u2 1
this_class(类索引) u2 1
super_class(父类索引) u2 1
interfaces_count(所实现的接口个数) u2 1
interface(接口索引) u2 interfaces_count
fields_count(成员个数) u2 1
field(成员信息) field_info fields_count
methods_count(方法个数) u2 1
method(方法信息) method_info methods_count
attributes_count(属性个数) u2 1
attibute(属性信息) attribute_info attributs_count

2.1 魔数

0-3字节,表示它是否是【class】类型的文件(最开始的0000000是标号)
0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09

2.2 版本号

4-7字节,表示类的版本,第5和第6个字节是次版本号,第7和第8个字节是主版本号。
0x0034,即52表示是java8
0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09

2.3 常量池

类型 tag 描述
CONSTANT_utf8_info 1 UTF-8编码的字符串
CONSTANT_Integer_info 3 整型字面量
CONSTANT_Float_info 4 浮点型字面量
CONSTANT_Long_info 5 长整型字面量
CONSTANT_Double_info 6 双精度浮点型字面量
CONSTANT_Class_info 7 类或接口的符号引用
CONSTANT_String_info 8 字符串类型字面量
CONSTANT_Fieldref_info 9 字段的符号引用
CONSTANT_Methodref_info 10 类中方法的符号引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的符号引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MethodType_info 16 标识方法类型
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

8-9字节,表示常量池长度, 00 23(35)表示常量池#1-#34项,注意#0项不计入,也没有值。
0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09

第#1项0a表示一个Method信息,00 06和00 15(21)表示他引用了常量池中#6和#21项来获得这个方法的【所属类】和【方法名】。
0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09

第#2项09表示一个Filed信息,00 16(22)和00 17(23) 表示它引用了常量池中#22和#23来获得这个成员变量的【所属类】和【成员变量名】
0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09
0000020 00 16 00 17 08 00 18 0a 00 19 00 1a 07 00 1b 07

第#3项08表示一个字符串常量名称,00 18(24)表示它引用了常量中#24项
0000020 00 16 00 17 08 00 18 0a 00 19 00 1a 07 00 1b 07

第#4项0a表示一个Method信息00 19(25)和00 1a(26)表示它引用了常量池中#25和#26项来获得这个类的【所属类】和【方法名】
0000020 00 16 00 17 08 00 18 0a 00 19 00 1a 07 00 1b 07

第#5项07表示一个class信息,00 1b(27)表示它引用了常量池中的#27项
0000020 00 16 00 17 08 00 18 0a 00 19 00 1a 07 00 1b 07

第#6项07表示一个class信息,00 1c(28)表示它引用了常量池中的#28项
0000020 00 16 00 17 08 00 18 0a 00 19 00 1a 07 00 1b 07
0000040 00 1c 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29

第#7项01表示一个utf8串,00 03 表示长度,3c 69 6e 69 74 3e是【
0000040 00 1c 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29

第#8项 01 表示一个utf8串,00 03 表示长度,28 29 56是【()V】其实就是表示无参,无返回值。
0000040 00 1c 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29
0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e

第#21项 0c 表示一个【名+类型】,00 07 00 08 引用了常量池中#7 #8 两项
0000360 2e 6a 61 76 61 0c 00 07 00 08 07 00 1d 0c 00 1e

第#22项 07 表示一个Class信息,00 1d(29)引用了常量池中的#29 项
0000360 2e 6a 61 76 61 0c 00 07 00 08 07 00 1d 0c 00 1e

第23项 0c 表示一个【名+类型】,00 1e(30) 00 1f(31)引用了常量池中#30 #31两项
0000360 2e 6a 61 76 61 0c 00 07 00 08 07 00 1d 0c 00 1e
0000400 00 1f 01 00 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64

第#24项 01 表示一个utf8串,00 0f(15)表示长度,是【hello world】
0000400 00 1f 01 00 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64

第#28项 01 表示一个utf8串,00 10(16)表示长度,是【java/lang/Object】
0000460 6f 57 6f 72 6c 64 01 00 10 6a 61 76 61 2f 6c 61
0000500 6e 67 2f 4f 62 6a 65 63 74 01 00 10 6a 61 76 61

第#29项 01 表示一个utf8串,00 10(16)表示长度,是【java/lang/System】
0000500 6e 67 2f 4f 62 6a 65 63 74 01 00 10 6a 61 76 61
0000520 2f 6c 61 6e 67 2f 53 79 73 74 65 6d 01 00 03 6f

第#30项 01 表示一个utf8串,00 03表示长度,是【out】
0000520 2f 6c 61 6e 67 2f 53 79 73 74 65 6d 01 00 03 6f
0000540 75 74 01 00 15 4c 6a 61 76 61 2f 69 6f 2f 50 72

第#31项 01 表示一个utf8串,00 15(21)表示长度,是【Ljava/io/PrintStream;】
0000540 75 74 01 00 15 4c 6a 61 76 61 2f 69 6f 2f 50 72
0000560 69 6e 74 53 74 72 65 61 6d 3b 01 00 13 6a 61 76

2.4 访问标识和继承信息

image.png
image.png

2.5 Field信息

image.png

2.6 Method信息

image.png

反编译:javap -c -l name.class

2.7 附加属性

image.png

3 字节码指令

3.1 图解方法执行流程

1) 原始java代码

public class HelloWorld {
    public static void main(String[] args) {
        int a = 10;
        int b = Short.MAX_VALUE + 1;
        int c = a + b;
        System.out.println(c);
    }
}

2) 编译后的字节码文件

D:\IdeaProjects\test\target\classes\jvm>javap -v HelloWorld.class
Classfile /D:/IdeaProjects/test/target/classes/jvm/HelloWorld.class
  Last modified 2020-4-18; size 600 bytes
  MD5 checksum 6a2ead3e960137482393c58719f3360b
  Compiled from "HelloWorld.java"
public class jvm.HelloWorld
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#25         // java/lang/Object."":()V
   #2 = Class              #26            // java/lang/Short
   #3 = Integer            32768
   #4 = Fieldref           #27.#28        // java/lang/System.out:Ljava/io/PrintStream;
   #5 = Methodref          #29.#30        // java/io/PrintStream.println:(I)V
   #6 = Class              #31            // jvm/HelloWorld
   #7 = Class              #32            // java/lang/Object
   #8 = Utf8               
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               LocalVariableTable
  #13 = Utf8               this
  #14 = Utf8               Ljvm/HelloWorld;
  #15 = Utf8               main
  #16 = Utf8               ([Ljava/lang/String;)V
  #17 = Utf8               args
  #18 = Utf8               [Ljava/lang/String;
  #19 = Utf8               a
  #20 = Utf8               I
  #21 = Utf8               b
  #22 = Utf8               c
  #23 = Utf8               SourceFile
  #24 = Utf8               HelloWorld.java
  #25 = NameAndType        #8:#9          // "":()V
  #26 = Utf8               java/lang/Short
  #27 = Class              #33            // java/lang/System
  #28 = NameAndType        #34:#35        // out:Ljava/io/PrintStream;
  #29 = Class              #36            // java/io/PrintStream
  #30 = NameAndType        #37:#38        // println:(I)V
  #31 = Utf8               jvm/HelloWorld
  #32 = Utf8               java/lang/Object
  #33 = Utf8               java/lang/System
  #34 = Utf8               out
  #35 = Utf8               Ljava/io/PrintStream;
  #36 = Utf8               java/io/PrintStream
  #37 = Utf8               println
  #38 = Utf8               (I)V
{
  public jvm.HelloWorld();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ljvm/HelloWorld;

  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: bipush        10
         2: istore_1
         3: ldc           #3                  // int 32768
         5: istore_2
         6: iload_1
         7: iload_2
         8: iadd
         9: istore_3
        10: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        13: iload_3
        14: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        17: return
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 7: 6
        line 8: 10
        line 9: 17
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      18     0  args   [Ljava/lang/String;
            3      15     1     a   I
            6      12     2     b   I
           10       8     3     c   I
}
SourceFile: "HelloWorld.java"

3) 常量池载入运行时常量池

image.png

4) 方法字节码载入方法区

image.png

5) main 线程开始运行,分配栈帧内存

image.png

6) 执行引擎开始执行字节码

执行bipush 10,将10压入操作数栈
istore 1将操作数栈栈顶数据10弹出,存入栈帧中局部变量表卡槽1
最后a=10

后面指令类同。

3.2 相关练习

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 demo3_8_2 = new Demo3_8_2("s3",30);
        System.out.println(demo3_8_2.a);  //s3
        System.out.println(demo3_8_2.b);  //30
    }
}

编译器会按照从上至下的顺序,收集所有{}代码块和成员变量赋值的代码,形成新的构造方法,但原始构造方法内的代码总是在最后。静态代码块->非静态代码块->类的构造方法。

a->s1->s2->s3
b->20->10->30

0: aload_0
1: invokespecial #1                  // Method java/lang/Object."":()V
4: aload_0
5: ldc           #2                  // String s1
7: putfield      #3                  // Field a:Ljava/lang/String;
10: aload_0
11: bipush        20
13: putfield      #4                  // Field b:I
16: aload_0
17: bipush        10
19: putfield      #4                  // Field b:I
22: aload_0
23: ldc           #5                  // String s2
25: putfield      #3                  // Field a:Ljava/lang/String;
28: aload_0
29: aload_1
30: putfield      #3                  // Field a:Ljava/lang/String;
33: aload_0
34: iload_2
35: putfield      #4                  // Field b:I
38: return

3.3 多态原理小结

当执行invokevirtual指令时(即调用对象的普通方法):

  1. 先通过栈帧中的对象引用找到对象
  2. 分析对象头,找到对象的实际Class
  3. Class结构中有vtable,它在类加载的链接阶段就已经根据方法的重写规则生成好了
  4. 查表得到方法的具体地址
  5. 执行方法的字节码

3.4 异常处理

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;
        }
    }
}
image.png

练习

public class Demo3_12_2 {
    public static void main(String[] args) {
        int result = test();
        System.out.println(result); //20
    }
    public static int test() {
        try {
            return 10;
        } finally {
            return 20;
        }
    }
}

由于finally中的ireturn被插入了所有可能的流程,因此返回结果肯定以finally的为准。

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

    public static int test() {
        int i = 10;
        try {
            return i;
        } finally {
            i = 20;
        }
    }
}
image.png

3.5 synchronized

public class Demo3_13 {
    public static void main(String[] args) {
        Object lock = new Object();
        synchronized (lock){
            System.out.println("ok");
        }
    }
}
image.png

你可能感兴趣的:(类加载与字节码)