class文件检查器

本文摘自:深入java虚拟机第二版<wbr style="line-height:25px"><div style="line-height:25px"> <div style="line-height:25px"> <span style="color:#0000ff; line-height:25px">Class文件检查器</span><span style="color:#003366; line-height:25px">保证装载的</span><span style="color:#0000ff; line-height:25px">class文件</span><span style="color:#003366; line-height:25px">内容有正确的内部结构,并且这些</span><span style="color:#0000ff; line-height:25px">class文件</span><span style="color:#003366; line-height:25px">互相间协调一致。</span><span style="color:#0000ff; line-height:25px">Class文件检查器</span><span style="color:#003366; line-height:25px">实现的安全目标之一就是程序的健壮性。如果某个有漏洞的编译器,或某个聪明的黑客,产生了一个</span><span style="color:#0000ff; line-height:25px">class文件</span><span style="color:#003366; line-height:25px">,而这个</span><span style="color:#0000ff; line-height:25px">class文件</span><span style="color:#003366; line-height:25px">中包含了一个方法,这个方法的字节码中含有一条跳转到方法之外的指令,那么,一旦这个方法被调用,它将导致虚拟机的崩溃,所以,处于对健壮性的考虑,由虚拟机检验它装载的字节码的</span><span style="color:#0000ff; line-height:25px">完整性</span><span style="color:#003366; line-height:25px">非常重要。</span> </div> <div style="line-height:25px"> <span style="color:#0000ff; line-height:25px"> </span><span style="color:#ff6600; line-height:25px">Class文件检验器</span><span style="color:#003366; line-height:25px">在字节码执行之前,必须完成大部分检验土作。它只在</span><span style="color:#0000ff; line-height:25px">执行前</span><span style="color:#003366; line-height:25px">而不是在执行中对字节码</span><span style="color:#ff6600; line-height:25px">进行一次分析</span><span style="color:#003366; line-height:25px">(并检验它的完整性),每一次遇到一个跳转指令时都进行检验。作为字节码确认工作的一部分,虚拟机将确认</span><span style="color:#0000ff; line-height:25px">所有的跳转指令</span><span style="color:#003366; line-height:25px">会到达</span><span style="color:#0000ff; line-height:25px">另一条合法的指令</span><span style="color:#003366; line-height:25px">,而且这条指令是在</span><span style="color:#0000ff; line-height:25px">这个方法</span><span style="color:#003366; line-height:25px">的</span><span style="color:#0000ff; line-height:25px">字节码流中</span><span style="color:#003366; line-height:25px">的:在大多数情况下,在执行前就对所有字节码进行一次检查,对于保证健壮性来说就足够了,而不必在它运行时何次都检验每一条字节码指令</span> </div> <div style="line-height:25px"> <span style="color:#ff6600; line-height:25px">Class文件检查器</span><span style="color:#003366; line-height:25px">要进行四趟独立的扫描来完成它的操作。</span> </div> <div style="line-height:25px"><span style="line-height:25px">第一趟:Class文件的结构检查</span></div> <div style="line-height:25px"> <span style="color:#003366; line-height:25px">在这一趟扫描中,对每一段将被当做</span><span style="color:#0000ff; line-height:25px">class</span><span style="color:#003366; line-height:25px">导入的字节序列,</span><span style="color:#ff6600; line-height:25px">Class文件检查器</span><span style="color:#003366; line-height:25px">都会确认它是否符合</span><span style="color:#0000ff; line-height:25px">Java</span><span style="color:#003366; line-height:25px">的</span><span style="color:#0000ff; line-height:25px">Class文件</span><span style="color:#003366; line-height:25px">的基本结构。在这一趟检查中,检查器会进行很多检查。例如:每个</span><span style="color:#0000ff; line-height:25px">Class文件</span><span style="color:#003366; line-height:25px">都必须以</span><span style="color:#0000ff; line-height:25px">四个同样的字节开始</span><span style="color:#003366; line-height:25px">:</span><span style="color:#ff6600; line-height:25px">0xCAFEBABE</span><span style="color:#003366; line-height:25px">。因为这个魔数</span><span style="color:#ff6600; line-height:25px">Class文件分析器</span><span style="color:#003366; line-height:25px">会很容易判断出某个文件具有明显问题而加以拒绝。检查器还必须确认在</span><span style="color:#0000ff; line-height:25px">Class文件</span><span style="color:#003366; line-height:25px">中声明的</span><span style="color:#0000ff; line-height:25px">版本号</span><span style="color:#003366; line-height:25px">和</span><span style="color:#0000ff; line-height:25px">次版本号</span><span style="color:#003366; line-height:25px">,这个版本号必须在这个</span><span style="color:#0000ff; line-height:25px">虚拟机</span><span style="color:#003366; line-height:25px">实现可以支持的范围之内。而且第一趟扫描还必须确认这个</span><span style="color:#0000ff; line-height:25px">Class文件</span><span style="color:#003366; line-height:25px">有没有被删减。虽然不同的class文件有不同的长度,但是在class文件中包含的每一个组成部分都声明了它的</span><span style="color:#0000ff; line-height:25px">长度</span><span style="color:#003366; line-height:25px">和</span><span style="color:#0000ff; line-height:25px">类型</span><span style="color:#003366; line-height:25px">。</span><span style="color:#ff6600; line-height:25px">检验器</span><span style="color:#003366; line-height:25px">可以使用class所有的组成部分的</span><span style="color:#0000ff; line-height:25px">类型</span><span style="color:#003366; line-height:25px">和</span><span style="color:#0000ff; line-height:25px">长度</span><span style="color:#003366; line-height:25px">来确定整个</span><span style="color:#0000ff; line-height:25px">class文件</span><span style="color:#003366; line-height:25px">的正确的总长度。用这种方法,它就可以检查一个装人的文件,其长度是否和它里面的内容相一致。</span> </div> <div style="line-height:25px"><span style="color:#000080; line-height:25px"> 总之第一趟扫描的目的就是保证这个字节序列正确的定义了一个class。它必须遵从Java的class文件的固定格式,这样它才能被编译成在方法区中的(基于实现的)内部数据结构。第二、第三和第四趟扫描不是在符合class文件格式的二进制数据上进行的,而是在方法区中,由实现决定的数据结构上进行的。</span></div> <div style="line-height:25px"><span style="line-height:25px">第二趟:类型数据的语义检查</span></div> <div style="line-height:25px"> <span style="color:#0000ff; line-height:25px">第二趟扫描</span><span style="color:#003366; line-height:25px">,检查器要查看每个组成部分,确认它们是否是其所属类型的实例,</span><span style="color:#0000ff; line-height:25px">它们的结构是否正确</span><span style="color:#003366; line-height:25px">。例如,方法描述符(它的返回类型,以及参数的类型和个数)在class 文件中被存储为一个字符串.这个字符串必须符合特定的上卜文无关文法。检验器对每个组成部分进行检查的目的之一是,为了确认每个方法描述符都是</span><span style="color:#0000ff; line-height:25px">符合特定语法的、格式正确</span><span style="color:#003366; line-height:25px">的字符串。</span> </div> <div style="line-height:25px"> <span style="color:#003366; line-height:25px"> 另外,还要检查这个类本身是否符合由</span><span style="color:#0000ff; line-height:25px">Java</span><span style="color:#003366; line-height:25px">编程语言规定的特定条件。例如,</span><span style="color:#0000ff; line-height:25px">检查器</span><span style="color:#003366; line-height:25px">强制规定</span><span style="color:#0000ff; line-height:25px">除Object类</span><span style="color:#003366; line-height:25px">以外的类必须有一个超类,或者检查final类有没有被子化等。还要检查常量池中的条目是合法的,而且常量池的所有索引必须指向正确类型的常量池条目。也就是说,</span><span style="color:#0000ff; line-height:25px">class 文件检验器</span><span style="color:#003366; line-height:25px">在运行时检查了一些</span><span style="color:#0000ff; line-height:25px">Java 语言</span><span style="color:#003366; line-height:25px">应该在编译时遵守的强制规则。因为检验器并不能确定</span><span style="color:#0000ff; line-height:25px">class 文件</span><span style="color:#003366; line-height:25px">是否是由一个善意的、没有漏洞的编译器产生的,所以它会检查每个class 文件,以确保这些规则得到遵守。</span> </div> <div style="line-height:25px"><span style="line-height:25px">第三趟:字节码验证</span></div> <div style="line-height:25px"> <span style="color:#000080; line-height:25px">这一趟是要确保采用任何路径在字节码流中都得到一个确定的操作码,并确保操作数栈总是包含正确的数值以及正确的类型。</span> </div> <div style="line-height:25px"> <span style="color:#003366; line-height:25px">在</span><span style="color:#ff6600; line-height:25px">Class 文件检验器</span><span style="color:#003366; line-height:25px">成功地进行了两趟检查后,它将把注意力放在字节码上,这一趟扫描被称为“</span><span style="color:#ff6600; line-height:25px">字节码验证</span><span style="color:#003366; line-height:25px">”。在这趟扫描中,</span><span style="color:#0000ff; line-height:25px">Java 虚拟机</span><span style="color:#003366; line-height:25px">对字节码流进行分析,这些字节流代表的是类的方法所对应的</span><span style="color:#0000ff; line-height:25px">JVM指令</span><span style="color:#003366; line-height:25px">。字节码流其实是</span><span style="color:#0000ff; line-height:25px">JVM指令</span><span style="color:#003366; line-height:25px">的集合,它是由被称为操作码的单字节指令组成的序列,每一个操作码后都跟着1个或多个操作数。</span><span style="color:#0000ff; line-height:25px">操作数</span><span style="color:#003366; line-height:25px">用于在Java虚拟机执行操作码指令时提供所需的额外的数据。</span><span style="color:#0000ff; line-height:25px">字节码检验器</span><span style="color:#003366; line-height:25px">必须保证</span><span style="color:#0000ff; line-height:25px">局部变量</span><span style="color:#003366; line-height:25px">在赋予合适的值以前不能被访问,而且类的</span><span style="color:#0000ff; line-height:25px">字段</span><span style="color:#003366; line-height:25px">中必须总是被赋子正确类型的值,类的方法被调用时总是传递正确数值和类型的参数。字节码检验器还必须保证每一个操作码都是合法的。也就是要保证每一个操作码都有合法的操作数,以及对于每一个操作码,都有合适类型的数值位于局部变量中或是在操作数栈中。这些仅仅是字节码检验器所做的大量检验1 作中的一小部分,在整个检验过程通过后,它就能保证这个字节码流可以被Java 虚拟机安全地执行。</span> </div> <div style="line-height:25px"> <span style="color:#003366; line-height:25px"> 在第一、第二、第三趟扫描中,</span><span style="color:#0000ff; line-height:25px">Class 文件检验器</span><span style="color:#003366; line-height:25px">可以保证导人的class文件构成合理,内在一致,符合Java 编程语言的限制条件,并且包含的字节码可以被Java 虚拟机</span><span style="color:#0000ff; line-height:25px">安全地执行</span><span style="color:#003366; line-height:25px">。如果</span><span style="color:#ff6600; line-height:25px">class 文件检验器</span><span style="color:#003366; line-height:25px">发现其中任何一点不正确,它将会</span><span style="color:#0000ff; line-height:25px">抛出一个错误</span><span style="color:#003366; line-height:25px">,这个class文件将不会被程序使用。</span> </div> <div style="line-height:25px"><span style="line-height:25px">第四趟:符号引用的验证</span></div> <div style="line-height:25px"> <span style="color:#003366; line-height:25px">在动态链接的过程中,如果包含在一个</span><span style="color:#0000ff; line-height:25px">Class文件</span><span style="color:#003366; line-height:25px">中的符号引用被解析时,</span><span style="color:#0000ff; line-height:25px">Class文件检查器</span><span style="color:#003366; line-height:25px">要进行</span><span style="color:#0000ff; line-height:25px">第四趟检查</span><span style="color:#003366; line-height:25px">。</span> </div> <div style="line-height:25px"> <div style="line-height:25px"> <span style="color:#003366; line-height:25px">在这趟检查中,</span><span style="color:#0000ff; line-height:25px">Java 虚拟机</span><span style="color:#003366; line-height:25px">将追踪那些引用(</span><span style="color:#000080; line-height:25px">从被验证的class到被它引用的class中</span><span style="color:#003366; line-height:25px">),以确保这些引用是正确的。因为第四趟扫描要扫描被检查的</span><span style="color:#0000ff; line-height:25px">class</span><span style="color:#003366; line-height:25px">之外的</span><span style="color:#0000ff; line-height:25px">class</span><span style="color:#003366; line-height:25px">,所有这次扫描需要装载新的类。大多数</span><span style="color:#0000ff; line-height:25px">Java 虚拟机</span><span style="color:#003366; line-height:25px">的实现采用</span><span style="color:#0000ff; line-height:25px">延迟装载类</span><span style="color:#003366; line-height:25px">的策略,直到类真正地被程序使用时才装载。即使为了加快装载过程的速度,而确实预先装载了这些类,第四趟扫描还是会表现为延迟。例如,如果</span><span style="color:#0000ff; line-height:25px">Java 虚拟机</span><span style="color:#003366; line-height:25px">在预先装载中发现它不能找到某个特定的被引用类,它并不在当时抛出</span><span style="color:#0000ff; line-height:25px">NotClassDefFoundError</span><span style="color:#003366; line-height:25px">错误,而是直到(或者除非)这个被引用类首次被运行程序使用时才抛出。这样,如果</span><span style="color:#0000ff; line-height:25px">Java 虚拟机</span><span style="color:#003366; line-height:25px">进行预先连接,</span><span style="color:#0000ff; line-height:25px">第四趟扫描</span><span style="color:#003366; line-height:25px">可以紧随</span><span style="color:#0000ff; line-height:25px">第三趟扫描</span><span style="color:#003366; line-height:25px">发生。但是如果</span><span style="color:#0000ff; line-height:25px">Java 虚拟机</span><span style="color:#003366; line-height:25px">在某个符号引用第一次被使用时才进行解析,那么</span><span style="color:#0000ff; line-height:25px">第四趟扫描</span><span style="color:#003366; line-height:25px">将在</span><span style="color:#0000ff; line-height:25px">第三趟扫描</span><span style="color:#003366; line-height:25px">以后很久、当字节码被执行时才进行。</span> </div> </div> <div style="line-height:25px"> <span style="color:#0000ff; line-height:25px">第四趟扫描</span><span style="color:#003366; line-height:25px">仅仅是动态链接过程的一部分。当一个</span><span style="color:#0000ff; line-height:25px">Class文件</span><span style="color:#003366; line-height:25px">被装载时,它包含了对其他类的符号引用以及它们的字段和方法。一个符号引用是一个字符串,它给出了名字,并且可能还包含了其他关于这个被引用项的信息------这些信息必须足以唯一的识别一个</span><span style="color:#0000ff; line-height:25px">类、方法、字段</span><span style="color:#003366; line-height:25px">。这样对于其他类的符号引用必须给出这个</span><span style="color:#0000ff; line-height:25px">类的全名</span><span style="color:#003366; line-height:25px">;对于其他类的字段的符号引用必须给出类名、字段名以及字段描述符;对于其他类中的方法的引用必须给出类名、方法名以及方法的描述符。</span> </div> <div style="line-height:25px"> <span style="color:#003366; line-height:25px">所谓的</span><span style="color:#ff6600; line-height:25px">动态链接</span><span style="color:#003366; line-height:25px">是一个</span><span style="color:#0000ff; line-height:25px">将符号引用解析为直接引用的过程</span><span style="color:#003366; line-height:25px">。当Java虚拟机执行字节码时,如果它遇到一个操作码,而且这个操作码是第一次使用一个指向另一个类的符号引用,那么虚拟机就必须解析这个符号引用。在解析时,</span><span style="color:#0000ff; line-height:25px">虚拟机执行</span><span style="color:#003366; line-height:25px">两个基本任务:</span> </div> <div style="line-height:25px"><span style="color:#000080; line-height:25px">A、查找被引用的类(如果必要的话就装载它)。</span></div> <div style="line-height:25px"><span style="color:#000080; line-height:25px">B、将符号引用替换为直接引用,例如一个指向类、字段或方法的指钊或偏移星。</span></div> <div style="line-height:25px"> <span style="color:#0000ff; line-height:25px">虚拟机</span><span style="color:#003366; line-height:25px">必须记住这个直接引用,这样当它以后再次遇到相同的引用时,它就可以立即使用这个直接引用,而不必花时间再次解析这个符号引用了。当Java虚拟机解析一个符号引用时,</span><span style="color:#ff6600; line-height:25px">class 文件检验器</span><span style="color:#003366; line-height:25px">的第四趟扫描确保了这个引用是合法的.如果这个引用是个非法引用(例如,这个类不能被装载,或这个类的确存在,但是不包含被引用的字段或方法)的话,</span><span style="color:#ff6600; line-height:25px">class 文件检验器</span><span style="color:#003366; line-height:25px">将抛出一个错误。</span> </div> <div style="line-height:25px"> <span style="color:#003366; line-height:25px">此外,由于Java程序是</span><span style="color:#ff6600; line-height:25px">动态链接</span><span style="color:#003366; line-height:25px">的,所以</span><span style="color:#ff6600; line-height:25px">Class文件检查器</span><span style="color:#003366; line-height:25px">在进行第四次扫描中,必须检查相互引用类之间的</span><span style="color:#0000ff; line-height:25px">兼容性</span><span style="color:#003366; line-height:25px">。如果你修改了一个类,</span><span style="color:#ff6600; line-height:25px">Java编译器</span><span style="color:#003366; line-height:25px">常会重编译这些类,从而在编译时检测是否有任何的不兼容性。但是也有很多时候,编译器并不对受影响的类进行重编译,例如,如果正在开发一个大型系统,很可能将系统分割成几个部分放人包中。如果对每个包进行独立的编译,当改动包中的一个类时,可能将导致对同一个包内受影响的那些类</span><span style="color:#0000ff; line-height:25px">进行重新编译</span><span style="color:#003366; line-height:25px">,但是对其他包中受影响的类并</span><span style="color:#0000ff; line-height:25px">不进行重新编译</span><span style="color:#003366; line-height:25px">。 此外如果使用了其他人的包时,尤其是程序在运行时通过网络下载了一些类,就不可能在编泽时</span><span style="color:#0000ff; line-height:25px">检验兼容性</span><span style="color:#003366; line-height:25px">。这就是为什么class文件检验器在第四趟扫描时,必须在运行时</span><span style="color:#0000ff; line-height:25px">检查兼容性</span><span style="color:#003366; line-height:25px">的原因。</span> </div> </div> </wbr>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值