JVM类加载机制详解(一)JVM类加载过程

首先Throws(抛出)几个本身学习过程当中一直疑惑的问题:java

一、什么是类加载?何时进行类加载?编程

二、什么是类初始化?何时进行类初始化?数组

三、何时会为变量分配内存?安全

四、何时会为变量赋默认初值?何时会为变量赋程序设定的初值?网络

五、类加载器是什么?数据结构

六、如何编写一个自定义的类加载器?学习

 

首先,在代码编译后,就会生成JVM(Java虚拟机)可以识别的二进制字节流文件(*.class)。而JVM把Class文件中的类描述数据从文件加载到内存,并对数据进行校验、转换解析、初始化,使这些数据最终成为能够被JVM直接使用的Java类型,这个说来简单但实际复杂的过程叫作JVM的类加载机制测试

 

Class文件中的“类”从加载到JVM内存中,到卸载出内存过程有七个生命周期阶段。类加载机制包括了前五个阶段。spa

以下图所示:code

其中,加载、验证、准备、初始化、卸载的开始顺序是肯定的,注意,只是按顺序开始,进行与结束的顺序并不必定。解析阶段可能在初始化以后开始。

 

另外,类加载无需等到程序中“首次使用”的时候才开始,JVM预先加载某些类也是被容许的。(类加载的时机

 

1、类的加载

咱们日常说的加载大多不是指的类加载机制,只是类加载机制中的第一步加载。在这个阶段,JVM主要完成三件事:

 

一、经过一个类的全限定名(包名与类名)来获取定义此类的二进制字节流(Class文件)。而获取的方式,能够经过jar包、war包、网络中获取、JSP文件生成等方式。

二、将这个字节流所表明的静态存储结构转化为方法区的运行时数据结构。这里只是转化了数据结构,并未合并数据。(方法区就是用来存放已被加载的类信息,常量,静态变量,编译后的代码的运行时内存区域)

三、在内存中生成一个表明这个类的java.lang.Class对象,做为方法区这个类的各类数据的访问入口。这个Class对象并无规定是在Java堆内存中,它比较特殊,虽为对象,但存放在方法区中。

 

2、类的链接

类的加载过程后生成了类的java.lang.Class对象,接着会进入链接阶段,链接阶段负责将类的二进制数据合并入JRE(Java运行时环境)中。类的链接大体分三个阶段。

一、验证:验证被加载后的类是否有正确的结构,类数据是否会符合虚拟机的要求,确保不会危害虚拟机安全。

二、准备:为类的静态变量(static filed)在方法区分配内存,并赋默认初值(0值或null值)。如static int a = 100;

静态变量a就会在准备阶段被赋默认值0。

对于通常的成员变量是在类实例化时候,随对象一块儿分配在堆内存中。

另外,静态常量(static final filed)会在准备阶段赋程序设定的初值,如static final int a = 666;  静态常量a就会在准备阶段被直接赋值为666,对于静态变量,这个操做是在初始化阶段进行的。

完成了验证阶段以后,就进入准备阶段。准备阶段是正式为变量分配内存空间而且设置类变量初始值

须要注意的是,这时候进行内存分配的仅仅是类变量(也就是被static修饰的变量),实例变量是不包括的,实例变量的初始化是在对象实例化的时候进行初始化,并且分配的内存区域是Java堆。这里的初始值也就是在编程中默认值,也就是零值。

例如public static int value = 123 ;value在准备阶段后的初始值是0而不是123,由于此时还没有执行任何的Java方法,而把value赋值为123的putStatic指令是程序被编译后,存放在类构造器clinit()方法之中,把value赋值为123的动做将在初始化阶段才会执行。

特殊状况:若是类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量就会被初始化为ConstantValue属性所指定的值,例如public static final int value = 123 编译时javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将变量赋值为123。

三、解析:将类的二进制数据中的符号引用换为直接引用。

 

3、类的初始化

类初始化是类加载的最后一步,除了加载阶段,用户能够经过自定义的类加载器参与,其余阶段都彻底由虚拟机主导和控制。到了初始化阶段才真正执行Java代码。

类的初始化的主要工做是为静态变量赋程序设定的初值。

如static int a = 100;在准备阶段,a被赋默认值0,在初始化阶段就会被赋值为100。

 

Java虚拟机规范中严格规定了有且只有五种状况必须对类进行初始化

一、使用new字节码指令建立类的实例,或者使用getstatic、putstatic读取或设置一个静态字段的值(放入常量池中的常量除外),或者调用一个静态方法的时候,对应类必须进行过初始化。

二、经过java.lang.reflect包的方法对类进行反射调用的时候,若是类没有进行过初始化,则要首先进行初始化。

三、当初始化一个类的时候,若是发现其父类没有进行过初始化,则首先触发父类初始化。

四、当虚拟机启动时,用户须要指定一个主类(包含main()方法的类),虚拟机会首先初始化这个类。

五、使用jdk1.7的动态语言支持时,若是一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,而且这个方法句柄对应的类没有进行初始化,则须要先触发其初始化。

 

注意,虚拟机规范使用了“有且只有”这个词描述,这五种状况被称为“主动引用”,除了这五种状况,全部其余的类引用方式都不会触发类初始化,被称为“被动引用”。

 

被动引用的例子一:

经过子类引用父类的静态字段,对于父类属于“主动引用”的第一种状况,对于子类,没有符合“主动引用”的状况,故子类不会进行初始化。代码以下:

 

 
  1. //父类

  2. public class SuperClass {

  3. //静态变量value

  4. public static int value = 666;

  5. //静态块,父类初始化时会调用

  6. static{

  7. System.out.println("父类初始化!");

  8. }

  9. }

  10.  
  11. //子类

  12. public class SubClass extends SuperClass{

  13. //静态块,子类初始化时会调用

  14. static{

  15. System.out.println("子类初始化!");

  16. }

  17. }

  18.  
  19. //主类、测试类

  20. public class NotInit {

  21. public static void main(String[] args){

  22. System.out.println(SubClass.value);

  23. }

  24. }

输出结果:

 

被动引用的例子之二:

经过数组来引用类,不会触发类的初始化,由于是数组new,而类没有被new,因此没有触发任何“主动引用”条款,属于“被动引用”。代码以下:

 

 
  1. //父类

  2. public class SuperClass {

  3. //静态变量value

  4. public static int value = 666;

  5. //静态块,父类初始化时会调用

  6. static{

  7. System.out.println("父类初始化!");

  8. }

  9. }

  10.  
  11. //主类、测试类

  12. public class NotInit {

  13. public static void main(String[] args){

  14. SuperClass[] test = new SuperClass[10];

  15. }

  16. }

 

 

没有任何结果输出!

 

 

被动引用的例子之三:

刚刚讲解时也提到,静态常量在编译阶段就会被存入调用类的常量池中,不会引用到定义常量的类,这是一个特例,须要特别记忆,不会触发类的初始化!

 

 
  1. //常量类

  2. public class ConstClass {

  3. static{

  4. System.out.println("常量类初始化!");

  5. }

  6.  
  7. public static final String HELLOWORLD = "hello world!";

  8. }

  9.  
  10. //主类、测试类

  11. public class NotInit {

  12. public static void main(String[] args){

  13. System.out.println(ConstClass.HELLOWORLD);

  14. }

  15. }

 

相关文章
相关标签/搜索