Java虚拟机类加载方式

来源:互联网 发布:python subprocess 编辑:程序博客网 时间:2024/04/30 14:52
Java中,在调用类的静态成员,或新建该类的对象等之前,类一定要先装入Java虚拟机中,这是勿庸置疑的。但虚拟机怎样把类装载进来的呢?要经过三步:装载(Load),链接(Link),初始化(Initializ)。其中链接又可分为校验(Verify),准备(Prepare),解析(Resolve)三步。

JVM类加载机制

一、装载(Load)
ClassLoader就是用来装载的。通过指定的className,找到二进制码,生成Class实例,放到JVM中。
ClassLoader从顶向下分为 Bootstrap ClassLoader、Extension ClassLoader、System ClassLoader以及User-Defined ClassLoader(分叉,可以多个)。如下图。
JVM类加载机制

这是Tomcat装载器的例子:
JVM类加载机制


装载过程从源码清析可见:
protected synchronized Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    // 先检查是否已被当前ClassLoader装载。
    Class c = findLoadedClass(name);
    if (c == null) {
        try {
        if (parent != null) {
            // 如果没被当前装载,则递归的到父中装载。
            c = parent.loadClass(name, false);
        } else {
           // 装载器树已到顶,还没找到的话就到Bootstrap装载器中找。注意:虽然Bootstrap是所有加载器的根,但它是C++实现的,不可能放到子的"parent"中,因此,第二层装载器是所有的根了。
            c = findBootstrapClass0(name);
        }
        } catch (ClassNotFoundException e) {
            // 如果祖先都无法装载,则用当前的装载。子类可在findClass方法中调用defineClass,把从自定义位置获得的字节码转换成Class。
            c = findClass(name);
        }
    }
    if (resolve) {
        // Links the specified class.
        resolveClass(c);  // 注
    }
    return c;
}

注:
1. resolveClass(c)方法的注释是链接类,而不只是解析,从该即可看出。
调用resolveClass时语义上是去链接,是否真的链接了我不是很清楚,但可以肯定的是没有初始化。当A类中有static B b=new B()时,最晚会在初始化时去装载B。
如果改成static B b=null,那么把B.class删掉后,即使A已经链接,初始化过了,但也不会报错,也就是A所引用的B类没有被加载过。解析时难道没有真的去装入它所引用的B类?还是链接时,没有执行解析的步骤? 问题的关键就是 1.对“解析”的理解,解析时是否会去装载B类?2. JVM在链接时是否执行了解析?(毕竟有资料说,解析是可选步骤)

二、链接
链接就是把load进来的class合并到JVM的运行时状态中

链接 是三个阶段中最复杂的一个。可以把它分成三个主要阶段:

  • 校验。 对二进制字节码的格式进行校验,以确保格式正确、行为正确。
  • 准备。 准备类中定义的字段、方法和实现接口所必需的数据结构。比如会为类中的静态变量赋默认值(int等:0, reference:null, char:'\u0000')。
  • 解析。 装入类所引用的其他所有类。可以用许多方式引用类:
    • 超类
    • 接口
    • 字段
    • 方法签名
    • 方法中使用的本地变量
三、初始化
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其实没有被初始化。

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

2. 类加载器可能不同
Class.forName(String) 方法(只有一个参数), 使用调用者的类加载器来加载, 也就是用加载了调用forName方法的代码的那个类加载器。当然,它也有个重载的方法,可以指定加载器。 相应的, ClassLoader.loadClass()方法是一个实例方法(非静态方法), 调用时需要自己指定类加载器, 那么这个类加载器就可能是也可能不是加载调用代码的类加载器(调用代用代码类加载器通getClassLoader0()获得)


参考资料:
1. http://java.sun.com/docs/books/jls/third_edition/html/execution.html#44487  这章是“Execution”,很详细讲了类装载过程,很权威!
2. http://www.ibm.com/developerworks/cn/java/j-dclp1/ 类装入问题解秘,不错。

3. http://baike.baidu.com/view/160708.htm  百度百科


转自:http://blog.sina.com.cn/s/blog_4fe01e630100gu3x.html

0 0