分类: Java
2010-08-07 13:27:05
class文件由字节码组成,一般由Java编译器生成,当然其他的一些字节码组成的文件也能充当class文件,这就要看它的具体内容。如果这个class文件由某个带有bug的编辑器生成,或者是被人恶意篡改了一些内容,比如在一个方法里悄悄加上跳转到方法之外指令,那么在执行这个class文件时,JVM将崩溃。文件校验器正是要保证class文件的内容、结构、完整性都符合JVM执行的要求。因此,它会在字节码执行之前进行class文件扫描,扫描分为四次:
1,class文件结构检查
此次扫描目的是确定class文件符合Java class的基本结构,比如文件是否以0XCAFEBABE这四个字节开头,文件是否包含正确的版本号和此版本号。我们在运行一些程序时偶尔会遇见java.lang.UnsupportedClassVersionError 错误,然后后面跟着一个版本号错误的提示信息,这就是class文件结构检查时抛出的错误,表明class文件是由另一个版本的Java编译器生成的,而这个版本的编译器不在当前的JVM支持范围以内。另外,这次扫描还会检查文件的完整性(头尾完整),以及文件中对数据结构长度信息的描述是否与文件内容总长度符合,以确保文件正确的定义了一个新的class类。
2,类型数据的语义检查
第二次扫描针对文件的各个组成部分,比如类的方法和属性。方法的返回类型、参数都会存储为一个字符串,这个字符串必须满足的Java规则,校验器将会校验所有的方法描述符(返回类型、参数、参数个数等),确定它们符合Java语法。另外,它还会对类本身应该遵循的规则进行校验,必须是否是Object或由Object类继承而来,是否继承了final类型的类,是否重写了final类型的方法,常量的定义是否正确。尽管编译器已经进行语法的检验,但是JVM还是需要再次进行校验,确定这些class文件是由可靠的编译器生成的。如果某位程序员恶意修改了编译器,使得它可以编译一些语法错误的Java文件,那么在这里将校验失败,并抛出一个错误。
3,字节码验证
class文件中,方法是由字节码流来表示的,这次扫描中JVM将对对字节码流进行分析。字节码流是由一些成为操作码组成的序列,而操作码后面则跟着一个或多个操作数。操作码、操作数的概念见
原文关于字节码验证一段:
大致意思是:class文件中Java的方法是字节码流代替的,字节码流就是流操作码序列,而操作码就是单字节指令,JVM中定义了220条这样的指令。操作码后面会紧随一个或多个操作数,操作码决定要完成的操作,操作数指参加运算的数据及其所在的单元地址。JVM会创建线程来执行字节码流,其实就是一个接一个执行字节码流中的操作码。每个线程都会分配一个栈,栈由许多帧组成。每次方法调用都将产生一个新的帧,帧就是一块内存区域,用来存储中间结果和局部变量。帧里面用来存储中间结果的部分称为操作数栈,操作码和操作数可能会用到操作数栈中的数据或者帧中的局部变量。这样,在执行操作码是时,JVM不但可以使用紧随在其后的操作数,还可以使用操作数栈中的数据,以及局部时变量。
校验器在这趟扫描里要做大量的工作,在执行字节码流中的一个特定操作码时,它保证无论如何执行,总能在操作数栈中得到一致的数据。校验器将确保局部变量在使用之前已经赋值,类的成员变量的赋值正确,类的成员方法的调用符合参数要求。校验器还必须保证,操作码、操作数以及在执行中将产生的操作数栈中的数据正确。字节码校验还有其他校验工作要做。
完成字节码校验以后,它将确保这个class文件安全的在JVM上执行。
经过1,2,3次扫描,class文件校验器可以保证class文件在结构到内容正确,满足java规范,包含的字节码可以在JVM上安全的执行。如果扫描中出现了问题,则会抛出错误,并阻止JVM执行。
4,符号引用的校验
前三次扫描发生在class文件执行以前,而第四次则发生在动态连接时。
动态链接时,class中的引用的符号将被解析,文件校验器必须保证引用正确,JVM将开始校验被引用的class文件。因为涉及到其他的class文件,类装载器可能需要装载这个class,JVM采用延迟装载机制,一直到class被使用时才会装载。有时为了提高装载速度,会进行预先装载,尽管存在预先装载,JVM还是表现出延迟装载的特征。比如预先装载时有一个引用的class文件找不到,那么它不会立即抛出NoClassDefFoundError,而是等到这个class被使用时再抛出。这样第四次扫描可能会紧跟着前三次进行,也可可能在前三次扫描完毕以后很久再执行。
符号引用校验包括符号的引用信息的校验,引用是否合法。比如引用的方法名称、参数是否正确,类名、成员变量名是否正确等。JVM在解析引用时进行以下操作:
A,查找被引用的类,必要的话装载它
B,将符号引用替换为直接引用,比如将指向方法的引用——方法名替换为指向方法的指针或偏移量。
JVM将记住这个直接引用,以便下次遇到相同的引用时,不必再进行解析。
另外,在第四次扫描中,校验器还将检查兼容性,比如在大型的系统中,class被拆分为许多包,其中一些包的修改并不会导致所有引用它的包的重新编译,这时候在运行时就会提示一些java.lang.NoSuchMethodError,
java.lang.NoSuchFieldError错误。
From: http://salever.javaeye.com/blog/717670