探索Java类的加载机制与初始化

加载生命周期
类从加载进入JVM内存到卸载出内存它经历的完整的生命周期是:加载、验证、准备、解析、初始化、使用、卸载。
加载过程
类的加载过程包括:加载、验证、准备、解析、初始化共五个阶段。加载过程需要注意的是:
1、解析的发生顺序是不确定的,因为Java支持动态绑定,解析也可能发生在初始化开始之后进行。
补充:Java的绑定分为静态和动态:静态绑定为Java编译期绑定,在Java中只有static、final、private以及构造方法是静态绑定,其他大部分都是动态绑定
2、除解析以外的其他是个过程的发生顺序是确定,但是并不能说它是按顺序进行或者完成的,因为解析过程可能发生互相调用,交叉进行的情况。
加载过程逐一解析
1、加载:
加载主要干3件事:
1、获取类的字节流:根据类的全限定名获取其定义的二进制字节流(不限于从Class文件中获取,还可以从jar等其他中获取)
2、结构转换:将这个二进制字节流代表的静态存储结构转换成方法区的运行时数据结构
3、在Java堆内存中生成一个对应的Class类对象,作为方法区数据访问入口
*补充:
在Java类的加载阶段相对于其他阶段来说,加载阶段的可控性是最强的,因为在这一阶段,我们不仅可以使用Java提供的类加载器,我们还能够自定义类加载器。

  • 对于JVM来说,类加载器可以分为两类:

    1 启动加载器:它是JVM本身的一部分
    2 其他所有的类加载器:这些加载器都继承自Java.lang.ClassLoader,它们独立于JVM之外,这些类加载器需要启动加载器来加载它们,然后它们才能够去加载其他的类

  • 对于开发人员来说,类加载器可以分为三类:
    1、启动加载器:BootStrap ClassLoader,它负责加载存放在JDK\jre\li(JDK 代表 JDK 的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如 rt.jar,所有的java.*开头的类均被 Bootstrap ClassLoader 加载)。启动类加载器是无法被 Java 程序直接引用的。
    2、扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载JDK\jre\lib\ext目录中,或者由 java.ext.dirs 系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。

    3、应用程序类加载器:Application ClassLoader,该类加载器由 sun.misc.Launcher$AppClassLoader 来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器

如果我们遇到一下需求:

  • 在执行非置信代码前,自动验证数字签名
  • 动态地创建符合用户特定需要的定制化构建类。
  • 从特定的场所取得 java class,例如数据库中和网络中。(Applet就用到了自定义的class loader)

我们还可以自定义Class Loader,这几种类加载器的关系是:
这里写图片描述
类加载器使用了双亲委派模型,这种模式不是利用继承来实现,而是用组合模式来复用父类中的代码的,这种模型的工作原理是:
如果一个类加载器接受到一个类加载请求,那么这个类不会立刻去加载类,它首先会把请求委托给父类去加载,依次向上,所以所有的请求都最终会由启动类加载器来加载,如果父类无法加载,那么才由子类来加载,依次向下。
对于任何一个类,JVM都要确定其唯一性,而唯一性的确定和该类以及加载该类的加载类有关,因此,加载类的双亲委派模型有一个明显的好处就是使类加载器有一个优先级关系,这种优先级关系很好的保障了Java程序运行的稳定性,例如:一个类加载器接受到请求时,会依次向上委托,如果启动类加载器能够加载这个类,那么就保证了所有的这种请求都是由启动类加载来加载,这就保证了类在加载过程中的唯一性。
2、验证
验证是为了保证此字节流所包含的信息符合虚拟机的要求,并且对虚拟机本身是安全的
验证分为以下四种:

  • 文件格式的验证:验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理,该验证的主要目的是保证输入的字节流能正确地解析并存储于方法区之内。经过该阶段的验证后,字节流才会进入内存的方法区中进行存储,后面的三个验证都是基于方法区的存储结构进行的。
  • 元数据验证:对类的元数据信息进行语义校验(其实就是对类中的各数据类型进行语法校验),保证不存在不符合 Java 语法规范的元数据信息。
  • 字节码验证:该阶段验证的主要工作是进行数据流和控制流分析,对类的方法体进行校验分析,以保证被校验的类的方法在运行时不会做出危害虚拟机安全的行为。
  • 符号引用验证:这是最后一个阶段的验证,它发生在虚拟机将符号引用转化为直接引用的时候,主要是对类自身以外的信息(常量池中的各种符号引用)进行匹配性的校验。

3、准备
是为类变量分配内存并设置类变量初始值的阶段,这个时候的内存分配仅包括static类变量,而不包括实例变量,实例变量是在实例化得时候进行内存分配
4、解析
解析阶段是虚拟机将常量池中的符号引用转化为直接引用的过程
解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行,分别对应于常量池中的 CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info 四种常量类型。
5、初始化

  • 初始化的时间:是类加载过程的最后阶段,当发生以下情况的任意一种的时候都会触发类的初始化:
    1、通过getstatic、putstatic、invokestatic指令,或者调用一个static方法,或者静态域被赋值的时候会初始化
    2、通过new关键字或者调用Java.lang.refect 包中的反射方法时
    3、初始化一个类时,会先初始化其super类
    4、当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先执行该主类。
    虚拟机规定只有这四种情况才会触发类的初始化,称为对一个类进行主动引用,除此之外所有引用类的方式都不会触发其初始化,称为被动引用。下面举一些例子来说明被动引用。

    通过子类引用父类中的静态字段,这时对子类的引用为被动引用,因此不会初始化子类,只会初始化父类:

class Father{  
    public static int m = 33;  
    static{  
        System.out.println("父类被初始化");  
    }  
}  

class Child extends Father{  
    static{  
        System.out.println("子类被初始化");  
    }  
}  

public class StaticTest{  
    public static void main(String[] args){  
        System.out.println(Child.m);  
    }  
} 

结果:
父类被初始化
33

参考资料:

1. http://www.infoq.com/cn/articles/cf-Java-class-loader
2. http://wiki.jikexueyuan.com/project/java-vm/
3. http://www.importnew.com/6579.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章