ld链接时提示接口未实现_浅谈JVM类加载机制(二)——链接

        简单的来说,链接主要包括三个部分,验证(Verification)、准备(Preparation)、解析(Resolution)。

        Linking a class or interface involves verifying and preparing that class or interface, its direct superclass, its direct superinterfaces, and its element type (if it is an array type), if necessary. Resolution of symbolic references in the class or interface is an optional part of linking.—— 摘自JAVA SE 8 虚拟机规范 5.4

        链接包括了对一个类或者是接口,以及它的直接父类或者是直接父接口的验证和准备。如果有必要的话还会对这个类或者接口进行符号引用的解析(懒加载,未用到的类可以不进行加载)

        一、先来说说验证阶段,验证在整个jvm类加载过程中的工作量会占整个过程的绝大部分,它不仅仅需要校验class文件字节码的结构格式,还需要对字节码进行验证,防止载入恶意代码导致虚拟机系统崩溃。这里只拿出对class文件的验证一些进行讨论(规范里写的实在是太多了),基本上可以分为四类。详细的可以转到javaSE8虚拟机规范的第四章进行阅读

ClassFile {    u4             magic;    u2             minor_version;    u2             major_version;    u2             constant_pool_count;    cp_info        constant_pool[constant_pool_count-1];    u2             access_flags;    u2             this_class;    u2             super_class;    u2             interfaces_count;    u2             interfaces[interfaces_count];    u2             fields_count;    field_info     fields[fields_count];    u2             methods_count;    method_info    methods[methods_count];    u2             attributes_count;    attribute_info attributes[attributes_count];}

         1、文件格式的校验,这个阶段主要是为了验证class文件是否符合类文件结构(可以不用理解,只需要知道会对文件格式进行校验即可)

            ·    是否以魔数0xCAFEBABE开头,这里的魔数指的是magic部分

            ·    该文件的注次版本号是否被当前虚拟机的类文件格式版本所支持,minor_version

跟major_version分别代表的是这个类文件的副版本号与主版本号,设主版本号为M,副版本号为m,则需要用支持从Mi.0到Mj.m的虚拟机进行加载

            ·    验证常量池的索引是否有效

         2、元数据的验证,这个阶段主要是进行语言规范分析,判断字节码所描述的信息是否符合java语言规范

            ·    this_class的值必须是常量池表中的有效索引,它表示这个类文件定义的类或者是接口(对于constant_pool索引来说,constant_class_info中的tag项的值应该是CONSTANT_CLASS,name_index项必须是格式为CONSTANT_Utf8__info的二进制类或接口名称)

            ·    判断super_class是否为0或者是常量池表中的有效索引,如果为0,则该类一定是java.lang.Object类,当一个类作为父类存在时,access_flags这个标志栏上不能设置ACC_FINAL标志(final类不能被继承)

            ·    字段表、方法表中只包含自己类/接口的方法、字段,并不会包含父类的方法、字段,同时,如果method_info索引的信息结构中(method_info {u2 access_flags; u2 name_index ......})如果并未设置为ACC_NATIVE(本地方法)或者是ACC_ABSTRACT(抽象方法),则必须提供该方法的实现指令(方法体)

         3、字节码的验证,简要地说,就是对方法体的验证,保证能够正常并且安全地执行。

            ·    代码不会在执行字节码指令的中途结束

            ·    任何指令不能跨域修改或者是数组越界访问(如,电视跟人类毫无相关的继承关系,但是将电视的对象引用赋值给了人类;数组越界访问就是数组长度为3,但是可以通过下标3进行数据访问)

            ·    保证代码结构的正确性(顺序结构、分支结构、循环结构)

        4、符号引用验证,这个校验发生在将符号引用转换为直接引用的解析阶段,主要是将类中所使用到的常量池符号引用进行验证,如:是否可以通过该符号引用描述的字符串全限定名查找到目标类;在目标类是否存在该方法/字段(具体会在解析阶段进行讲解)

        二、准备阶段

·       Preparation involves creating the static fields for a class orinterface and initializing such fields to their default values ——摘自 JAVA SE8 虚拟机规范 5.4.2

        准备工作将为类/接口的静态字段分配空间并为其赋上默认值,但这并不会为该静态字段给予指定的值。在准备阶段,只会为其分配一个内存空间。

public class Test {  static int i1 = 10;  static final int i2 = 11;  static final String i3 = "hello";  static final String i4 = new String("hello");}

        通过javap查看这个Test类的class文件,截取两段,一段是静态变量描述的字节码段,一段是类初始化的字节码段,我们可以看到的是,对于i1,i4来说,只是进行了描述与标识,分配空间,真正的赋值需要在类初始化中进行(static{}中配合方法进行类初始化)。对于i2,i3,在字段信息中多出了一项 ConstantValue,这就表示这两个字段在编译期就已经确定值了(对于只被final修饰的字段也一样)。对于被static final修改的基础类型以及String类型(不能通过new String("")的方式赋值,因为new会触发初始化,而准备阶段必须在初始化阶段前完成 “Preparation may occur at any time followingcreation but must be completed prior to initialization.”),会被放入到常量池中,而调用时通过常量池获取,所以不会触发类的初始化。

  static int i1;    descriptor: I    flags: ACC_STATIC  static final int i2;    descriptor: I    flags: ACC_STATIC, ACC_FINAL    ConstantValue: int 11  static final java.lang.String i3;    descriptor: Ljava/lang/String;    flags: ACC_STATIC, ACC_FINAL    ConstantValue: String hello  static final java.lang.String i4;    descriptor: Ljava/lang/String;    flags: ACC_STATIC, ACC_FINAL      // 下面开始是类初始化的字节码  static {};    descriptor: ()V    flags: ACC_STATIC    Code:      stack=3, locals=0, args_size=0         0: bipush        10         2: putstatic     #2                  // Field i1:I         5: new           #3                  // class java/lang/String         8: dup         9: ldc           #4                  // String hello        11: invokespecial #5                  // Method java/lang/String."":(Ljava/lang/String;)V        14: putstatic     #6                  // Field i4:Ljava/lang/String;        17: return

        三、解析

        The java Virtual Machine instructions anewarray,checkcast,getfield,getstatic,instanceof, invokestatic,invokevirtual, ldc, ldc_w, multianewarray, new, putfield, and putstatic make symbolic references to the run-time constant pool. Execution of any of these instructions requires resolution of its symbolic reference.——摘自JAVA SE8 虚拟机规范 5.4.3

        当这些字节码anewarray、multianewarray(实例化数组),new(new对象),getfield、putfield、invokespecial、invokevirtual(操作实例属性、方法),getstatic、putstatic、invokestatic(操作静态方法),ldc、ldc_w(常量池取值并进行压栈操作),checkcast(类型检查),instanceof(判断是否为另一个实例并进行压栈操作)对运行时常量池进行符号引用的时候,这些指令的执行都需要被解析成直接引用。、

        何为符号引用,何为直接引用?现在有类A、类B、以及入口类类Main,然后我通过入口类进行A类的加载(仅做加载,但不会引发接下来的链接过程),之后查看A类的常量池可以看到一个“JVM_CONSTNAT_UnresolvedClass ”的值为字符串的B类全限定名“com/z/test/B”,这个时候它这个值的状态就是处于符号引用的状态,只是一个描述符,不能通过这个去找到对应的类。

public class A {  B b = new B();}
public class B {}
public class Main {  public static void main(String[] args)       throws ClassNotFoundException, IOException {        ClassLoader classLoader = ClassLoader.getSystemClassLoader();        Class> c = classLoader.loadClass("com.z.test.A");        //new A();        System.in.read();    }}

2b2f620ed2f85b8e8f42ada21c162ee4.png

而如果我将new A()这段代码放开的话,将会触发类A的对象实例化操作,也会引起类的链接,初始化操作,这时候在通过HSDB查看A类的常量池的话,会发现,UnresolvedClass都变成了Class,而且当时的符号引用也变成了直接引用,能通过该引用在方法区查找到对应的类

79de70bffc3cc6283117f7781473bb2f.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值