在java中,在编译时把源码编译成.class文件,在运行时加载类。但是一直让人困惑的是:在运行时是什么时候对类进行加载,又是什么时候对类进行 初始化的呢?

在C/C++中,首先源文件被编译成机器码,然后再把不同的机器码文件通过连接形成可执行文件。

但是在java中,连接的过程是在类加载之后完成的。

java虚拟机把类加载进来要经过三个步骤:装载(Load),链接(Link),初始化(Initializ)。其中链接又可分为校验(Verify),准备(Prepare),解析(Resolve)三步。

在两种情况下需要对类进行装载:

  1. 调用类的静态成员
  2. 新建该类的对象

类的装入

显式装入与隐式装入

类装入的方式有两种 —— 显式 或 隐式,两者之间有些细微差异。

显式

cl.loadClass()
• (
cl
•  是 
java.lang.ClassLoaderClass.forName()
  • (启动的类装入器是当前类定义的类装入器)

当调用其中一个方法的时候,指定的类(以类名为参数)由类装入器装入。如果类已经装入,那么只是返回一个引用;否则,装入器会通过委托模型装入类。

隐式

类的装入通常组合了显式和隐式类装入。例如,类装入器可能先显式地装入一个类,然后再隐式地装入它引用的所有类。

Class.forName()与ClassLoader.loadClass()这两方法都可以通过一个给定的类名去定位和加载这个类名对应的 java.long.Class 类对象,区别如下:1. 初始化Class.forName()会对类初始化,而loadClass()只会装载或链接。可见的效果就是类中静态初始化段及字节码中对所有静态成员的初始工作的执行(这个过程在类的所有父类中递归地调用). 这点就与ClassLoader.loadClass()不同. ClassLoader.loadClass()加载的类对象是在第一次被调用时才进行初始化的。你可以利用上述的差异. 比如,要加载一个静态初始化开销很大的类, 你就可以选择提前加载该类(以确保它在classpath下), 但不进行初始化, 直到第一次使用该类的域或方法时才进行初始化2. 类加载器可能不同Class.forName(String) 方法(只有一个参数), 使用调用者的类加载器来加载, 也就是用加载了调用forName方法的代码的那个类加载器。当然,它也有个重载的方法,可以指定加载器。 相应的, ClassLoader.loadClass()方法是一个实例方法(非静态方法), 调用时需要自己指定类加载器, 那么这个类加载器就可能是也可能不是加载调用代码的类加载器(调用代用代码类加载器通getClassLoader0()获得)

类装入器委托

类装入器委托模型 是把装入请求相互传给对方的类装入器图。引导 类装入器是这个图的根。用单一委托父类

  • 缓存(Cache)
  • 父类(Parent)
  • 自己(Self)

类装入器首先判断要求它装入的类是否与过去装入的类相同。如果相同,就返回上次返回的类(即保存在缓存中的类)。如果不是,就把装入类的机会交给父类。这两步递归地以深度优先的方式重复。如果父类返回 null(或抛出  ClassNotFoundException

),那么类装入器会在自己的路径中寻找类的源。

因为父类类装入器总是先得到装入类的机会,所以类装入器装入的类最靠近根。这意味着所有核心引导类都是由引导装入器(Bootstrap Class Loader)装入的,这就保证装入了类(例如 java.lang.Object)的正确版本。这也可以让类装入器看到自己或父类或祖先装入的类,但是不能看到子女装入的类。

类装入器委托模型

基本(primordial) 类装入器)不能由 Java 代码实例化。(通常是因为它是作为 VM 本身的一部分实现的。)这个类装入器可以从启动的类路径装入核心系统类,通常是位于 jre/lib 目录的 JAR 文件。但是能用 -Xbootclasspath

扩展(extension) 类装入器(也称作标准扩展

系统(system) 类装入器(也称作应用程序 类装入器)负责从 CLASSPATH 环境变量指定的路径装入代码。默认情况下,这个类装入器是用户创建的任何类装入器的父类。这也是 ClassLoader.getSystemClassLoader()

链接

链接

  • 字节码验证。
  • 类准备。
  • 解析。 在这个阶段,类装入器装入类所引用的其他所有类。可以用许多方式引用类:
  • 超类
  • 接口
  • 字段
  • 方法签名
  • 方法中使用的本地变量
  • 类的初始化

在初始化

在这三个阶段末尾,类被完整地装入,可以使用了。请注意可以用惰性方式执行类装入,所以类装入过程的某些部分可能在第一次使用类的时候才执行,而不是在装入时执行。

Initialization of a class consists of executing its static initializers and the initializers forstatic fields (class variables) declared in the class. Initialization of an interface consists of executing the initializers for fields (constants) declared there.
类的初始化包括:执行静态区块和静态方法的初始化。比如下面这两种代码都会被执行,包括new B()。
 static{
   ...
 }
 static B b=new B();

接口中不允许有static initializer(也就是static{...}),所以对于接口,只会执行静态字段的初始化。

初始化前,装载,链接一定已经执行过!

类初始化前,它的直接父类一定要先初始化(递归),但它实现的接口不需要先被初始化。类似的,接口在初始化前,父接口不需要先初始化。

什么情况下,类的初始化会被触发?

A class or interface type T will be initialized immediately before the first occurrence of any one of the following:

  • T is a class and an instance of T is created.
  • T is a class and a static method declared by T is invoked.
  • A static field declared by T is assigned.
  • A static field declared by T is used and the field is not a constant variable (§4.12.4).
  • T is a top-level class, and an assert statement (§14.10) lexically nested within T is executed.

class Super { static int taxi = 1729; }
 class Sub extends Super { static { System.out.print("Sub "); } }
 class Test {
   public static void main(String[] args) {
              System.out.println(Sub.taxi);
  
 }

只会输出“1729”,不会输出"Sub",也就是说,Sub其实没有被初始化。

参考文献:

  1. http://www.ibm.com/developerworks/cn/java/j-dclp1/
  2. http://blog.sina.com.cn/s/blog_4fe01e630100gu3x.html