JAVA类的加载与初始化

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


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

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

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


JVM类加载机制

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

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

  • 类的装入

显式装入与隐式装入

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

显式 类装入发生在使用以下方法调用装入的类的时候:

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

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

隐式 类装入发生在由于引用、实例化或继承导致装入类的时候(不是通过显式方法调用)。在每种情况下,装入都是在幕后启动的,JVM 会解析必要的引用并装入类。与显式类装入一样,如果类已经装入了,那么只是返回一个引用;否则,装入器会通过委托模型装入类。

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


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) 类装入器(也称作标准扩展 类装入器)是引导类装入器的一个孩子。它的主要职责是从扩展目录装入类,通常位于 jre/lib/ext 目录。这提供了简单地访问新扩展的能力,例如不同的安全扩展,不需要修改用户的类路径即可实现。

系统(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



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值