第7章虚拟机类加载机制
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的JAVA类型,这就是虚拟机的类加载机制。
类加载的生命周期包括:加载Loading, 验证Verification, 准备Preparation, 解析Resolution, 初始化Initialization, 使用Using和卸载Unloading.
除解析阶段外,其他几个阶段的顺序都是固定的。解析阶段在某些情况下可以在初始化阶段之后再开始,这是为了支持JAVA语言的运行时绑定(动态绑定/晚期绑定)
虚拟机规范严格规定了有且只有四种情况必须对类进行初始化(加载,验证,准备自动在之前开始)
1.遇到new,getstatic,putstatic,invokestatic这4条字节码指令时,如
果类没有进行初始化,则先初始化。这4个字节码常见的出现场景是:使用new关键字实例化对象的时候,读取或设置静态字段(被final修饰,已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
2.反射调用时
3.初始化一个类时,如果其父类还未初始化,则先出发父类初始化。
4.当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先初始化这
个主类
这4种情况称为对类的主动引用,其他情况称为被动引用。
对于访问静态字段,只有直接定义这个字段的类才被初始化,因此通过子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。但是对于HOTSPOT,会触发子类的加载。
通过数组定义引用类,不会触发此类的初始化。
常量在编译阶段会存入调用类的常量池,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
接口的加载和类加载过程稍有不同,接口不能有static代码段,但接口中还是会生成
类加载的过程
加载
在加载阶段,虚拟机需要完成以下三件事情:
1.通过一个类的全限定名来获取定义此类的二进制字节流。
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3.在JAVA堆中生成一个代表着各类的ng.Class对象,作为方法区
这些数据的访问入口
事实上,这三条限定都不是很严格,比如第一条,并没有明确指出通过全限定名从哪里得到二进制流,由此就有很多不同的实现:
∙在ZIP包中读取(JAR,EAR,WAR)
∙从网络中获取(APPLET)
∙运行时计算生成,这种场景使用的最多的就是动态代理技术,在ng.reflect.Proxy中,就是用了
ProxyGenerator.generateProxyClass来为特定接口申城$Proxy的代理
类的二进制流
∙由其它文件生成(jsp)
∙从数据库中读取,有些中间件服务器(SAP NETWEAVER)
加载阶段完成后,虚拟机外部的二进制流就按照虚拟机所需的格式存储在方法区中,方法区中的数据存储格式由虚拟机实现自行定义。然后在JAVA堆中实例化一个ng.Class类对象,这个对象将作为程序访问方法区中的这些类型数据的外部接口。加载阶段与连接阶段的部分内容是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始。
验证
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并却不会危害虚拟机自身的安全。
一些在编译层面上可以控制的事情(比如超边界访问数组,跨类型进行类型对象转换存在时,编译器是拒绝工作的)可以通过直接修改class文件的方式进行破解,这就是验证阶段存在的原因。
按照虚拟机规范,如果验证到输入的字节流不符合Class文件的存储格式,就抛出一个ng.VerifyError异常或其子类异常。
大致分成4个阶段的验证过程:文件格式验证、元数据验证、字节码验证和符号引用验证
文件格式验证:
比如是否以魔数开头,主次版本号是否在虚拟机可处理范围之内,常量池是否有不支持类型等。
经过这个阶段的验证之后,字节流才会进入内存的方法区进行存储,所以后面的三个验证阶段全部是基于方法区的存储结构进行的。
元数据验证:
对字节码描述的信息进行语义分析,以保证其描述的信息符合JAVA语言规范的要求,这个阶段可能包括的验证点有:
这个类是否有父类,父类是否集成了不允许继承的类,如果不是抽象类是否实现了其父类或接口中要求实现的所有方法,类中的字段和父类是否有矛盾
字节码验证:
最复杂的一个解读那,主要工作是进行数据流和控制流分析。这阶段对类的方法体进行校验分析,保证该方法在运行时不会做出危害JVM安全的行为,例如:保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,保证跳转指令不会跳转到方法体以外的字节码指令上,保证方法体中的类型转换是有效的。。。这个验证并不能保证一定安全(停机问题,通过程序去校验程序逻辑是无法做到绝对准确的)
1.6加入StackMapTable功能对这个阶段做了优化,提高速度,但这个StackMapTable也可能被篡改,可以通过启动参数来关闭这个选项。
符号引用验证:
这个阶段发生在虚拟机将符号引用转化为直接引用的时候。这个转化动作将在连接的第三个阶段----解析阶段中发生。
可以看作是对类自身以外的信息进行匹配性的校验。
比如:符号引用中通过字符串描述的全限定名是否能找到对应的类,是否存在所描述的方法和字段。。。。
如果无法通过符号验证,将会抛出一个
ng.IncompatibleClassChangeError异常的子类,比如
ng.IllegalAccessError,ng.NoSuchFieldError,ng.NoS uchMethodError
可以使用启动参数来关闭大部分类验证措施,缩短虚拟机类加载时间。
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。这个时候内存分配的仅包括类变量(static变量),不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中。其次是这里所说的初始值“通常情况下”是数据类型的零值(随后在初始化阶段生成定义的初值)。如果该变量被final修饰,将在编译时生成ConstantValue,这样在准备阶段将直接设置成该初值。