类加载机制

来源:互联网 发布:面膜种草知乎 编辑:程序博客网 时间:2024/06/17 23:23

参考了《深入理解java虚拟机》

看不懂的可以先去看看我的java内存区域和class文件结构。

与那些在编译时需要进行连接工作的语言不同,在Java语言里面,类型的加载、连接和初始化过程都是在程序运行期间完成的,这种策略虽然会令类加载时稍微增加一些性能开销,但会为Java应用程序提供高度的灵活性,
1,类加载的时机
类从被加载到虚拟机内存中开始,到卸载出内存为止生命周期如下
这些阶段通常都是互相交叉地混合式进行的,
类的加载,Java虚拟机规范并没有进行强制约束,可以由虚拟机自由把握,但是初始化,虚拟机规范严格规定了有且只有5种情况必须立即对类进行初始化,
1,遇到下面这些
a,使用new关键字实例化对象的时候
b,c,读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)
d,以及调用一个类的静态方法的时候
2,对类进行反射调用的时候,如果类没有被初始化,就需要先触发其初始化
3,当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化(但是一个接口初始化时,并不要求其父接口全部完成了初始化)
4,当虚拟机启动时,用户需要制定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个类
5,
对于静态字段,只有直接定义这个字段的类才会初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。(调用类中static{}包含的静态代码块可以用类名.value来调用密码)
2,类加载的过程
1,加载(是类加载的一个阶段)
加载阶段虚拟机需要完成的几件事情,
a,通过一个类的全限定名来获取定义此类的二进制字节流(没有说明从哪里获取如从ZIP中读取、从网络上获取(最典型是Applet)、运行时计算生成(动态代理)、由其它文件生成(JSP文件生成的对应的Class类)、从数据库中读取)
b,把这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
c,在内存中生成一个代表这个类的java.lang.Class对象。作为方法区这个类的各种数据的访问入口
开发人员通过定义自己的类加载器去控制字节流的获取方式(即重写一个类加载器的loadClass()方法)
加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,方法区中的数据存储格式由虚拟机实现自行定义,虚拟机规范未规定此区域的具体数据结构,然后在内存中实例化一个java.lang.Class类的对象(并没有明确规定是在Java堆中,Class对象比较特殊,他虽然是对象,但存放在方法区的里面)这个对象将作为程序访问方法区的这些类型数据的外部接口,
2,验证
验证是连接阶段的第一步,这个阶段的目的是为了确保Class文件的字节流的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
上文提到Class文件并不一定要求用java源码编译而来,可以使用任何途径产生,所以需要验证,
a,文件格式验证,验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟处理,(如,是否以魔数开头、主次版本号是否在当前虚拟机处理的范围之内、常量池的常量中是否有不被支持的常量类型、指向常量的各种索引值是否有指向不存在的常量或不符合类型的常量、编码是否符合(UTF8)、Class文件中的各个部分及文件本身是否有被删除或附加的其他信息。。。。。。等等)
b,元数据验证(对字节码描述的信息进行语义分析,保证其描述的信息符合Java语言规范的要求如:这个类是否有父类、这个类是否继承了不允许被继承的类、如果这个1类不是抽象类是否实现了其父类或接口中要求实现的所有方法,)
c,字节码验证(验证阶段最复杂的一个阶段,通过数据流和控制流分析,确定程序语义是合法的符合逻辑的)
d,符号引用验证(可以看做是对类自身以外的信息进行匹配性的校验,如通过字符串描述的全限定名是否能找到对应的类、符号引用中的类、字段、方法的访问性是否可被当前类访问)
这个阶段和很重要但不一定必要如果所运行的全部代码(包括自己编写的及第三方包中的代码)都已经被反复使用和验证过那么可以考虑用参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间
3,准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配(这时候分配的仅仅包括类变量(就是被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中)当然这里的初始值“通常情况”下是数据类型的零值假设一个类变量为public static int Value=123;但是这个时候尚未开始执行任何Java方法,所以在准备阶段的初始化为0而不是为123,赋值为123的指令是在程序被初始化才会执行,当然如果是被final修饰的类变量在准备阶段就会把Value的值设为123,
4,解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,
符号引用是一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可
直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是能间接定位到目标的句柄
解析动作主要是针对类和接口、字段、类方法、接口方法、方法类型、方法句柄和调用点
java语言是一门静态类型语言
类和接口的解析:加载这个类或者接口加载的过程中也要进行验证,然后在进行一下符号引用验证确认是否对其有访问权限
字段解析:先在本身找是否有匹配的的字段,然后就去父类和实现的接口否则会一直向上查找,否则的话会抛出NoSuchFieldError异常找到了就会返回引用,然后验证是否有访问权限。
类方法解析:和字段解析有点类似现在本身类寻找、然后再去父类和,找到后返回直接引用,然后验证是否有访问权限
接口方法解析(接口的父类也是object)和上面有点相似,在本接口中找寻,找不到就到父接口找,找不到抛出异常,找到后返回引用,因为接口中所有方法都是public修饰的所以不存在访问权限的问题,
5,初始化
类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义来加载器的参与之外,其余动作完全由虚拟机主导和控制,到了初始化阶段才真正的开始执行类中定义的Java程序代码
准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,是执行类构造器<clinit>()方法的过程,clinit()方法是编译器自动收集类中的所有类变量1的赋值动作和静态语句块中的语句合并成的,是按顺序收集的,定义在静态语句块后面的变量可以被前面的静态语句块赋值但不能被访问,
虚拟机会保证一个类的<clinit>()方法再多线程中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他的都需要阻塞等待,直到活动线程执行<cinit>()方法完毕,当活动线程退出这个方法后其他线程不会再次进入同一个类加载器下,一个类型只会初始化一次


原创粉丝点击