关闭

类加载过程

标签: 类加载过程虚拟span idtransmarksp
81人阅读 评论(0) 收藏 举报
分类:

Class文件中的各种信息都必须加载到虚拟机中之后才能运行和使用,虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。与那些在编译时需要进行连接工作的语言不通,在Java语言里面,类型的加载、连接、初始化过程都是在程序运行期间完成的,这种策略会是类加载时增加一些性能开销,但会为java程序提供高度的灵活性,Java可以动态扩展的语言特性就是依赖运行期动态加载和动态连接这个特点实现的。

类从被加载到内存中开始,到卸载出内存,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用、卸载这七个阶段,其中验证、准备和解析这三个部分统称为连接。加载、验证、准备、初始化、卸载这五个阶段的顺序是确定的,类加载过程必须按照这种顺序按部就班的开始。对于解析阶段则不一定,它在某些情况下可以在初始化之后再开始,这是为了支持Java语言的运行时绑定。对于类加载过程的第一个阶段加载可以有虚拟机的具体实现来决定什么时候加载,而对于初始化阶段,虚拟机规划则严格规定了有且只有五种情况必须立刻进行初始化:

(1)遇到new、getstatic 、putstatic、invokestatic这四条字节码指令时,如果类没有进行初始化就需要先触发其初始化。生成这四条指令常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰,已在编译期把结果放入常量池的静态字段除外)以及调用一个类的静态方法时。

(2)使用reflect包的方法对类进行反射调用的时候,如果类没有初始化,则先需触发其初始化。

(3)当初始化一个类的时候,如果发现其父类还没有初始化,则需先触发其父类初始化

(4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main())方法的那个类,虚拟机会先初始化这个类

(5)当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果是REF_getStatic 、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

--------好了,说了这么多,下面说说类加载的过程------------

类加载过程分为三个阶段,分别为加载、连接、初始化。其中连接又分为三个阶段,分别是验证、准备、解析。

(1)加载是类加载过程的第一个阶段,这个阶段虚拟机需要完成三件事:

a:通过一个类的全限定名来获取定义类的二进制字节流

b:将这个字节流所代表的静态存储结构转发为方法区的运行数据结构

c:在内存中生成一个代表这个类的Class对象,作为方法区这个类的各种数据的访问入口。

(2)验证是类加载过程的第二个阶段,也是连接的第一个阶段,这个阶段的主要作用是确保Class文件的字节流包含的信息服务当前虚拟机的要求,并且不会危害虚拟机自身的安全。

(3)准备阶段是类加载过程的第三个阶段,也是连接的第二个阶段,这个阶段的主要作用是为类变量分配内存并设置类变量的初始值,这些变量所使用的内存都将在方法区分配。这里注意两点:第一是进行内存分配的是类变量(被static修饰)而不是实例变量,实例变量会在对象初始化时随对象一起分配在Java堆中。第二这里所说的初始值指的是数据类型的零值,而不是我们设置的初始值,我们自己设置的初始值在初始化阶段赋值。

(4)解析阶段是类加载过程的第四个阶段,也是连接的第三个阶段。这个阶段的主要作用是将常量池中的符号引用替换为直接引用的过程。符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能构无歧义的定位到目标即可。直接引用:可以直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。

(5)初始化阶段是类加载过程的最后一个阶段。其主要作用是将类变量初始化。在前面的类加载过程中,除了在加载阶段用户引用程序可以通过自定义的类加载器参与外,其余动作完全有虚拟机主导实现,到了初始化阶段,才是真正执行类中定义的程序代码。初始化阶段是执行类构造器<clinit>()方法的过程。

a:<clinit>()方法是由编译器自动收集所有的类变量赋值动作和静态语句块中的语句合并而成,编译器收集的顺序是由语句在源文件中出现的顺序所决定的。静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量只可以赋值,不能访问。

b:<clinit>()方法与类的构造方法不同,它不需要显示的调用父类构造器,虚拟机保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法执行完毕。

c:<clinit>()方法对类或接口并不是必须的。如果类中没有类变量赋值操作也没有静态语句块,那么编译器可以不为这个类生成<clinit>()方法

d:接口中没有静态语句块,但是有变量初始化的赋值操作,因此接口也会生成<clinit>()方法。但接口与类不同的是,执行接口的<clinit>()方法方法不需要先执行父接口的<clinit>()方法,只有当父接口中定义的变量使用时,父接口才会初始化,另外,接口的实现类在初始化的时候也不执行接口的<clinit>()方法。

e:虚拟机保证一个类的<clinit>()方法在多线程中被正确的加锁、同步。如果多个线程同时去初始化一个类,那么只会有一个类去执行这个类的<clinit>()方法,其他的线程都阻塞等待。


0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:198次
    • 积分:31
    • 等级:
    • 排名:千里之外
    • 原创:3篇
    • 转载:0篇
    • 译文:0篇
    • 评论:0条
    文章分类
    文章存档