jvm类加载机制过程

一、类加载过程

        Java类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中准备、验证、解析3个部分统称为连接(Linking)。如图所示:

图一:类加载流程图

      1.1、加载阶段

            虚拟机JVM需要做三件事情:

            1.1.1、通过一个类的全限定名来获取定义此类的二进制字节流(并没有指明要从一个Class文件中获取,可以从其他渠道,譬如:网络、动态生成、数据库等);

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

            1.1.3、在内存中生成一个代表这个类的java.lang.Class对象 [可通过类.class获取该对象],作为方法区这个类的各种数据的访问入口。



       1.2、连接阶段

            虚拟机JVM连接阶段分三步走:

            1 .2.1、验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:文件格式验证、元数据验证、字节码验证和符号引用验证。

            1.2.2、准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配,这时候进行内存分配的仅包括类变量(被static修饰的变量)。其次,这里所说的初始值“通常情况”下是数据类型的零值(null或零)。至于“特殊情况”是指:public static final int value=110,会直接初始化为指定的值 110。

            1.2.3、解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。



       1.3、初始化阶段

             初始化阶段是执行类构造器()方法的过程.

            ()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块只能访问到定义在静态语句块之前的变量.    

            ()方法与实例构造器()方法不同,它不需要显示地调用父类构造器,虚拟机会保证在子类()方法执行之前,父类的()方法方法已经执行完毕.

            tip:

            1.3.1、父类中定义的静态语句块要优先于子类的变量赋值操作;

            1.3.2、如果一个类或接口中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成()方法;

            1.3.3、接口与类不同的是,执行接口的()方法不需要先执行父接口的()方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的()方法;

            1.3.4、虚拟机会保证一个类的()方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。如果在一个类的()方法中有好事很长的操作,就可能造成多个线程阻塞,在实际应用中这种阻塞往往是隐藏的。


        1.4、卸载阶段

            虚拟机执行卸载类需要满足以下三个条件之一:

            1.4.1、 加载该类的ClassLoader已经被回收。 

            1.4.2、该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。

            1.4.3、该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。


下面以一种问答方式进一步了解加载机制:

    一、能简单点描述什么是类加载嘛?

            应该能。类加载是:通过类加载装载器ClassLoader将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。 通过该class对象可以获知Class的结构信息:如构造函数,属性和方法等

    二、类加载装载器又是什么嘛?

            ClassLoader负责对类的加载,读取 Java 字节代码,并转换成java.lang.Class类的一个实例,最后对该类进行管理。

    三、可以深入点讲解类加载器嘛?听说有三种类型那么多呢。

            可以的。java自身提供的确实有三种类型。其中包括了:

            3.1、启动类加载器,Bootstrap ClassLoader,加载\jre\lib\rt.jar,或者被-Xbootclasspath参数限定的类;

            3.2、扩展类加载器,Extension ClassLoader,加载\jre\lib\ext,或者被java.ext.dirs系统变量指定的类;

            3.3、应用程序类加载器,Application ClassLoader,加载ClassPath中的类库;

            当然,javaer们还可以自定义类加载器,通过继承ClassLoader实现,一般是加载我们的自定义类。

类加载器

            类加载器通过双亲委派模型,在每次收到类加载请求时,先将请求委派给父类加载器完成(所有加载请求最终会委派到顶层的Bootstrap ClassLoader加载器中),如果父类加载器无法完成这个加载,子类再尝试自己加载。

        四、为什么要用双亲委派模型将请求发送给父类来处理呢?

                这个问的真是好。这种模式的存在必然有它的充分的理由。原因是:类加载器决定了类的唯一性。意思是,同一份的字节代码,被不同的类加载器加载之后,所得到的类是不同的。假设,我们不使用双亲委派模型,将请求交由父类处理,那么就是自身处理咯。那在这个大前提下,再假设我们定义两个自定义加载类MyCLod1和MyCLod2,它们加载一个先生自己写的类MyClass。根据类加载器决定类的唯一性,那么在方法区中会出现两份不一样的类,那么后面我们该选择那份来使用呢?而当将这个MyClass类统一交给父类加载,只存在一份类,即不存在选择使用的情况。同时,也避免同一个类被多次加载,占用内存的情况。

        五、准备阶段有类变量初始化、初始化阶段也有对类变量的初始化,两者有什么不同嘛?

                前者是将类变量初始化位Java默认值零或null,后者是初始化程序员设置的类变量逻辑初始值。不一样的。

        六、那初始化阶段触发条件是什么呢?面霸经常遇到这样的问题呢。

                触发条件有6个那么多:

                6.1、为一个类型创建一个新的对象实例时(比如new、反射、序列化);

                6.2、调用一个类型的静态方法时(即在字节码中执行invokestatic指令);

                6.3、调用一个类型或接口的静态字段,或者对这些静态字段执行赋值操作时(即在字节码中,执行getstatic或者putstatic指令),不过用final修饰的静态字段除外,它被初始化为一个编译时常量表达式;

                6.4、调用JavaAPI中的反射方法时(比如调用java.lang.Class中的方法,或者java.lang.reflect包中其他类的方法);

                6.5、初始化一个类的派生类时(Java虚拟机规范明确要求初始化一个类时,它的超类必须提前完成初始化操作,接口例外);

                6.6、JVM启动包含main方法的启动类时;

        七、为什么需要类卸载阶段?

            不要说为什么提出这么幼稚的问题,我之前还真没认真想过这个问题呢。要回答这个问题不得不,追溯到Java语言创建之初衷,为嵌入式应用开发。当时嵌入式机器普遍内存小,不能让已经没用的类再占用紧张的内存,所以存在类卸载机制。

        八、最后贴一段代码,嘿嘿嘿。就给图,不直接给代码,自己手撕更容易理解结果输出的原理。(关键提示:可以从静态变量执行循序着手)

手撕代码图



    我是先生,找寻着那位迷路的Miss。最后,愿各位 javaer ,合上电脑的刹那,有着侠客收剑入鞘的骄傲!

你可能感兴趣的:(jvm类加载机制过程)