类加载过程详解

来源:互联网 发布:2017继续教育网络效应 编辑:程序博客网 时间:2024/04/29 13:34

类加载总共有三个阶段:加载 -> 连接(验证 -> 准备 -> 解析) -> 初始化。

java虚拟机规范并没有对何时适合进行加载操作进行详细的规定。但对于初始化阶段,虚拟机严格规定了5中情况下会开始初始化操作(那么加载,连接操作必然会先进行):

1.遇到new, getstatic, putstatic 或者invokestatic.  这三条指令分别对应使用new关键字实例化一个对象,读取或设置一个静态字段,调用类的静态方法。

2.使用java.lang.reflect包的方法对类进行反射调用。如:Class.forName(""),这是后如果类没有进行初始化,那么需要先触发其初始化。

3.当初始化一个类的时候,如果发现父类没有初始化,先触发父类的初始化

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

5.使用jdk1.7的动态语言支持时,由于对于这方面不太熟悉,就不详细说了

因为遇到这这几种情况,必然会触发类的初始化过程,那么就必然先执行加载,连接操作。现在以Student  student = new Student()为例:

程序遇到new这条指令,将要对Student类进行初始化,如果已经进行了初始化操作,那么直接在堆中创建对象就行,如果没有初始化,那么开始执行加载

加载阶段,虚拟机主要完成以下3件事:

1.通过一个类的全限定名获取定义此类的二进制流.(即可以直接从class文件中获取,也可以从zip包中获取,甚至可以从网络中获取)

2.将这个字节流代表的静态存储结构转化为方法区的运行时数据结构

3.在内存中生成一个代表该类的java.lang.class对象,作为方法区这个类的各种数据的访问入口。

加载阶段完成后,class文件中的编译时常量池转化为方法区中的运行时常量池,同时在方法区中实例化了一个代表该Student类的Class对象

接下来连接操作开始执行,连接过程又分为验证,准备,解析三个阶段:

验证:主要验证当前class文件是否符合虚拟机的规范,确保不会对虚拟机构成威胁

准备:正式为类变量分配内存和设置初始值(各种数据类型的0值)。这些类变量的都存储在方法区中,如下:

private static String name = "wsh";

那么这时候就会在方法区中为name变量分配内存,并将初始值设置为null.

但如果该类变量定义为如下形式:

private static final String name = "wsh"

那么在这个阶段会直接将初始值设置一个指向常量池中字符串"wsh"的地址的引用。

解析:解析阶段主要是将常量池中的符号引用替换成直接引用的过程。(对这一过程不太熟悉)

当所有连接操作都完成后,那么就开始执行初始化操作。初始化也是类加载过程的最后一步。在前面的类加载阶段中,所有动作基本都是虚拟机自主进行,但在初始化阶段,系统会根据程序员的意愿去初始化类变量和其他资源,例如对于

        private static String name = "wsh"

在连接操作执行完毕后,name指向的是一个null,但在初始化阶段,系统会为name变量执行初始化操作,使该变量保存一个执行常量池中字符串"wsh"的引用。

到这一步,所有的类加载动作都已经完成了。 这时虚拟机会为对象在堆内存中分配内存,对象所需要的内存大小在类加载过程可以完全确定。内存分配完成后,虚拟机开始将实例变量初始化为0值(不包括对象头),这一步主要是保证实例字段可以不经赋值直接使用,接下来,虚拟机开始对对象头进行一些设置,

使它保存一些类的元素据信息,对象的哈希码,对象的gc分代年龄等。这时一个初步的对象就已经创建完毕,只差最后一步,<init>方法还没执行。

此时系统开始执行<init>方法,将实例变量按照程序员的意愿进行初始化。到这里,一个对象的类加载过程过对象创建就已经完全结束。

现在我们可以尝试总结以下整个流程:

加载 -> 连接(验证 -> 准备 -> 解析) -> 初始化(执行<cinit>方法) ->堆中分配对象内存 ->设置初始0值,并将对象头进行一些必要的设置 ->执行<init>方法

->对象创建成功。