类加载过程-----验证哪些内容
当我们编写Java代码,保存成.java文件结尾的格式,这份文档被成为Java源代码文件。我们可以使用JDK中的javac工具编译这份Java源代码文件。文件被编译后会生成.class结尾,文件名和源代码一致的文件。这份.class文件保存着Java代码转经过换后的虚拟机指令。当需要某个类时,虚拟机会加载class类到虚拟机中,这一过程被称为类加载过程。
一个Java类从开始到结束会经历7个过程。其中验证,准备和解析被称为连接过程。解析过程在某些过程会再初始化之后执行,这也是为了支持运行时绑定
今天来讲讲虚拟机class文件校验器如下情况,虚拟机帮我们做了哪些验证,来保证.class代码的安全性:
-
在.class文件被加载
-
被加载后(连接阶段)
-
以及在解析符号引用时
1.在Class文件被加载的时候,检验其将进行许多检查,比如检查每隔class文件是否以0xCAFEBABE开头。(这也是为什么Java的图标是一杯香浓的咖啡的原因吧)。编译器强制每隔class文件都以这样一个字节开可以很容易的分辨出某个文件是否有明显的问题。
除此之外检验其还会确认class文件中声明的主版本号和次版本号,这个版本号必须在当前Java虚拟机可以支持的范围内。
class文件中每个组成部分都声明了它的长度和类型。检验其可以根据组成部分的类型和长度确定整个class文件的正确的总长度,这样检验其很容易发现class文件内容是否有删节,尾部是否附带其他的字节等情况。
这一趟的检验主要目的是保证这个字节序列正确的定义了一个新类型,它必须遵从Java的class文件的固定格式。如果检查没有问题后,class文件将被编译成方法去中的内部数据结构。后面的检查不是在符合class文件格式的二进制数据上进行,而是在方法区中的数据结构上进行的。
2.连接阶段将进行两趟独立的检查,类型数据的语义检查,字节码检查。
语义检查:校验器检查除Object类以外的所有类,是否都必须有一个超类。还要检查final类没有被子类化,而且final方法没有被覆盖。也就是说class文件检验器在运行时检查了一些Java语言应该在编译时遵守的强制规则,确保class文件是由一个没有漏洞的编译器产生的。
字节码检查:Java虚拟机对字节流进行数据分析,这些字节流代表的是类的方法。
字节码流代表了Java的方法,他是由被称为操作码的单字节指令组成的序列,每一个操作码后都跟着一个或多个操作数。操作数用于在Java虚拟机执行操作码指令时提供所需的额外的数据。
执行字节码时,依次执行每个操作码,这就在Java虚拟机内构成了执行的线程。每个线程被授予自己的Java栈,这个栈是由不同的栈帧构成。每一个方法调用将获得一个自己的栈帧,栈帧中存储这局部变量和计算的中间结果。
在栈帧中,用于存储方法的中间建国的部分被称为该方法的操作数栈。前面说到的操作数可能指存储在操作数栈中的数据或存储在方法栈帧中的局部变量中的数据。
字节码检查器会进行大量的检查,比如保证局部变量在赋予合适的值以前不能被访问,类中的字段中必须总是被赋予正确类型的值,类的方法被调用时总是传递正确的数值和类型的参数。
3.符号引用验证:
如果包含在class文件中的符号引用被解析时,class文件检验器将进行第四趟检查。大多数虚拟机都采用延迟装载类的策略,只有类被真正调用的时候才会解析。所以本次验证会再第三趟烧苗之后很久,字节码被执行时才进行。
什么是符号引用呢?一个符号引用是一个字符串,他给出了名字,并且可能包含其他关于这个被引用项的信息,这些信息必须足以唯一的识别一个类,字段或方法。这样,对于其他类的符号引用必须给出这个类的全限定名。对于其他类的字段的符号引用必须给出类名,字段名以及字段描述符。对于其他类中的方法的引用必须给出类名,方法名以及方法的描述符。
动态连接是一个将符号引用解析为直接引用的过程。直接引用,可以是指向类,字段或方法的指针或者偏移量。