java是一种类型安全的语言,它有四类称为安全沙箱机制的安全机制来保证语言的安全性,这四类安全沙箱分别是:
java
类加载体系安全
网络
.class文件检验器本篇博客主要介绍“.class文件检验器”的基本原理;如需了解其它几类安全机制能够经过上面的博客连接进入查看。性能
jvm的.class文件检验器用于检查.class文件是否拥有合法的内存结构,这种检查是有必要的,由于java的.class文件可能来自本机,也可能来自网络,多是你本身编译的文件,也多是别人篡改过的文件。而对于jvm来讲,一个.class文件就是一个字节序列,它不会过问字节序列的来源,只会校验字节序列的结构是否正确。ui
.class文件检验器保证安全的措施就是检验.class文件字节码的健壮性,好比某个.class文件是被恶意篡改过的,这个.class文件中包含一个方法,该方法有一条goto指令,直接跳到方法外部去执行未知的代码,若是执行该方法,极可能会致使jvm崩溃。因此,由.class文件检验器检查字节码的健壮性是颇有必要的。spa
虽然.class文件检验器检查字节码能保证程序的健壮性,而后这是须要牺牲一些性能的;为了将这种影响降到最低,.class文件检验器会在字节码执行以前完成大部分的检验工做,也就是说,.class文件检验器会在字节码执行前而不是执行中进行检查,并且这种检查只会进行一次。好比每次遇到一条跳转指令.class文件检验器都会确认该跳转指令跳转到了另一条合法指令,而该指令是是在同一方法的字节流中的。.net
.class文件检验器会进行四次独立的扫描来保证字节码的合法性。第一趟扫描在类装载时进行,此次会检查.class文件的内部结构,以保证它能被安全的编译;第二趟和第三趟扫描是在链接时进行的,这时会检查.class文件的数据定义是否听从了java语言的语义规范,还会检查字节码的完整性;第四趟扫描是在解析符号引用时进行的,此次会检查.class文件所引用的字段、方法和类是否存在。orm
在.class文件被装载时,会进行第一次扫描,这时.class文件检验器会检查每一条字节序列,看它是否符合java class文件的基本结构。首先,检验器会检查.class文件是否以魔数0XCAFEBABY开头,这个魔数的做用是为了区分一些明显错误的或被破坏的.class文件。而后,检验器会检查.class文件的主版本号和次版本号,这个版本号必须在jvm所支持的范围以内,好比我用java8编译编译的.class文件放在java6的jvm内执行会抛出java.lang.UnsupportedClassVersionError,由于java6不支持java8的一些新特性好比lambda表达式,因此不能去执行java8的编译器编译的字节码;而后java通常都是向后兼容的,好比java8的jvm是能执行java6编译的.class文件的。第一趟扫描主要是检查.class文件是否听从了java class文件的固定格式,这样才能字节码编译成方法区内的内部数据结构。
第二趟扫描会进行数据类型的语义检查,它不会再去检查.class文件的二进制数据了,而是会去检查方法区中定义的数据结构。类中定义的字段、方法和方法描述符等在.class文件中都会存储为一个字符串,检验器会检查这些字符串是否符合java规范;检验器还会检查类自己是否符合java语言规范,好比java语言规定除了Object类外全部类都必须有一个父类;检查器还会检查被final修饰的类(好比String.class)是否被继承,一样也会检查被final修饰的方法是否被覆盖,若是出现错误会抛出java.lang.VerifyError。检验器还会检查常量池里定义的常量是否合法,它会检查常量的索引会指向正确的常量池条目。
第三趟扫描会进行字节码验证,字节码检查会确认字节码的操做码的正确性,也会确保操做数栈包含正确的数值和类型,还会检查类的方法被调用时会传入正确的参数和参数类型。检验器在第三趟扫描会进行大量的操做,好比会检验全部的操做码都有一个合法的操做数等,在这趟扫描事后,它须要保证.class文件的字节码流能够被jvm安全地执行。而后检验器并不能检查出全部的安全问题,好比“停机问题”它就不能检查出来。停机问题是一个著名的计算机领域问题,即不能写出一个程序,用它来判断做为其输入的某个程序在执行时是否会停机。
第四趟扫描是在符号的动态链接时进行检查,检验器会检查符号引用是否合法;由于此次扫描须要检查该class类所引用的类,因此这是可能须要装载新的类;然而为了节约内存并保证程序正确性,jvm会使用延迟加载的策略来装载类,即直到类被程序真正使用时才会去装载。若是一个jvm实现为了加快装载速度预先装载了类,它仍是会表现为延迟装载。好比jvm在预装载某个类时发现这个类不存在,它并不会立刻抛出NoClassDefFoundError,而是直到这个缺乏的类被程序使用时才抛出异常。若是jvm进行预先链接,第四次扫描会紧接着第三次扫描马上执行;而若是jvm进行延迟链接(即在某个引用第一次执行时才链接),那么第四趟扫描可能在第三趟扫描以后好久才执行(甚至不执行)。