类加载

1.类加载的时机

  下图是类的生命周期,这里的类既指类class也指接口interface:

  

  其中,加载、验证、准备、初始化和卸载这五个阶段是按顺序开始的,解析则不一定,可能在初始化开始之后才开始。这里说“开始”是因为这些阶段可能是互相交叉地进行的。

  下面五种情况必须立即对类进行初始化(加载、验证、准备阶段自然要在之前进行):

  • 遇到new、getstatic、putstatic或invokestatic这4条指令的时候,如果类没进行过初始化,则需要先进行初始化。主要场景是使用new实例化对象的时候,读取或设置一个类的静态字段的时候,调用一个类的静态方法的时候
  • 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没进行过初始化,则需要先进行初始化
  • 当初始化一个类时,如果发现其父类还没初始化,则需要先触发其父类的初始化
  • 当虚拟机启动的时候,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先执行这个类的初始化
  • 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后解析结果REF_getstatic、REF_putstatic、REF_invokestatic的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化

  下面三个例子是不会触发初始化的:

public class SuperClass {
    static {
        System.out.println ("SuperClass init");
    }
    public static value = 123;
}

public class SubClass extends SuperClass {
    static {
        System.out.println ("SubClass init");
    }
}

public class NotInitialization {
    public static void main(String[] args) {
        System.out.println(SubClass.value);
    }
}

//output:
//SuperClass init

  对于静态字段,只有直接定义这个字段的类才会被初始化。至于子类是否被加载和验证,则不同虚拟机会有不同的实现。

public class NotInitialization {
    public static void main(String[] args) {
        SuperClass[] sca = new SuperClass[10];
    }
}
//no output

  并没有过触发“com...SuperClass”的初始化,而是触发“[Lcom...SuperClass”的初始化阶段,这是由虚拟机自动生成的类,创建动作有字符码newarray触发。

public  class ConstClass {
    static {
        System.out.println("ConstClass init");
    }
    public static final String HELLOWORLD = "hello world";
}

public class NotInitialization {
    public static void main(String[] args) {
        System.out.println(ConstClass.HELLOWORLD);
    }
}

//no "ConstClass init" output

  编译阶段已经通过常量传播优化,将常量的值存储到NotInitialization的常量池中。

 

2.类加载过程

  • 加载

  在加载阶段,虚拟机要完成三件事情:
  1) 通过类的全限定名获取此类的二进制字节流
  2) 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
  3) 在内存中生成一个代表这个类的Class对象,作为方法区中这个类各种数据的访问入口

  • 验证

  验证是连接阶段的第一步,目的是为了确保Class文件中的字节流所包含的信息符合虚拟机要求而且不会危害虚拟机自身安全。

  • 准备

  准备阶段是正式为类变量分配内存并设置类变量初始值的阶段。这里说的内存分配仅包括类变量,不包括实例变量,而设置初始值指的是设置零值。

  • 解析

  解析过程是将常量池内的符号引用替换成直接引用的过程。

  • 初始化

  到了初始化阶段才真正开始执行类中的Java代码。初始化阶段执行类构造器<clinit>()方法。

  <clinit>()方法由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{})中的语句合并产生,收集顺序是由原文件出现的顺序决定的。静态语句块只能访问定义在其前面的变量,赋值则没有限制。

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

  <clinit>()方法不是必需的,如果没有静态代码块也没有对变量的赋值操作,可以不生成<clinit>()。

  虚拟机会保证一个类的<clinit>()方法在多线程环境下被正确的加锁和同步。

 

3.类加载器

  类加载器是用来完成“通过类的全限定名获取此类的二进制字节流”这个过程的。

  

posted @ 2015-05-04 16:09  一同  阅读(154)  评论(0编辑  收藏  举报