JVM类加载机制

标签: jvm 加载 | 发表时间:2014-03-01 20:09 | 作者:zwllxs
出处:http://www.iteye.com
Java中,在调用类的静态成员,或新建该类的对象等之前,类一定要先装入Java虚拟机中,这是勿庸置疑的。但虚拟机怎样把类装载进来的呢?要经过三步:装载(Load),链接(Link),初始化(Initializ)。其中链接又可分为校验(Verify),准备(Prepare),解析(Resolve)三步。



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


这是Tomcat装载器的例子:



装载过程从源码清析可见:
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 for static 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  百度百科

已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [jvm 加载] 推荐:

JVM 类加载机制

- - 码蜂笔记
虚拟机类加载机制:把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型. 在Java语言里面,类型的加载、连接和初始化都是在程序运行期间完成的. 类的整个生命周期包括:加载(loading)、验证(verification)、准备(preparation)、解析(resolution)、初始化(initialization)、使用(using)和卸载(unloading) 7个阶段.

JVM类加载机制

- - Java - 编程语言 - ITeye博客
Java中,在调用类的静态成员,或新建该类的对象等之前,类一定要先装入Java虚拟机中,这是勿庸置疑的. 要经过三步:装载(Load),链接(Link),初始化(Initializ). 其中链接又可分为校验(Verify),准备(Prepare),解析(Resolve)三步. ClassLoader就是用来装载的.

JVM学习笔记(六):类加载的时机

- - ITeye博客
本文根据《深入理解java虚拟机》第7章部分内容整理.     Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的加载机制.    类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括了:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(using)、和卸载(Unloading)七个阶段.

JVM研究

- - 开源软件 - ITeye博客
每天接客户的电话都是战战兢兢的,生怕再出什么幺蛾子了. 我想Java做的久一点的都有这样的经历,那这些问题的最终根结是在哪呢. JVM全称是Java Virtual Machine,Java虚拟机,也就是在计算机上再虚拟一个计算机,这和我们使用 VMWare不一样,那个虚拟的东西你是可以看到的,这个JVM你是看不到的,它存在内存中.

jvm调优

- - 互联网 - ITeye博客
printf "%x\n" 21742  找到耗时最长的进程. jstack pid | grep 54ee  定位某个类的方法. jstack 10535|grep -A 10 2a1d (最后十行). jmap 查询pid 内存线程. 附:TOP命令中需要关注的值:. (1)load average:此值反映了任务队列的平均长度;如果此值超过了CPU数量,则表示当前CPU数量不足以处理任务,负载过高.

学习JVM的References

- LightingMan - 淘宝JAVA中间件团队博客
本blog中列举了我学习JVM的references,会不断的更新,为了避免版权问题,就不在blog上提供references的下载了,感兴趣的同学可自行下载或购买,:). |— [ Hotspot GC论文 ]. |— [ 其他JVM GC ]. |— Linux内核源代码情景分析. |— Linux 内核中断内幕.

深入理解JVM

- 小伟 - ITeye论坛最新讨论
1   Java技术与Java虚拟机. 说起Java,人们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成: Java编程语言、Java类文件格式、Java虚拟机和Java应用程序接口(Java API). 图1   Java四个方面的关系. 运行期环境代表着Java平台,开发人员编写Java代码(.java文件),然后将之编译成字节码(.class文件).

jvm垃圾回收

- Cano - 淘宝共享数据平台 tbdata.org
在jvm中堆空间划分为三个代:年轻代(Young Generation)、年老代(Old Generation)和永久代(Permanent Generation). 年轻代和年老代是存储动态产生的对象. 永久带主要是存储的是java的类信息,包括解析得到的方法、属性、字段等等. 我们这里讨论的垃圾回收主要是针对年轻代和年老代.

JVM内存分配

- - 移动开发 - ITeye博客
计算机内存,它算是CPU与计算机打交道最频繁的区域,所有数据都是先经过硬盘至内存,然后由CPU再从内存中获取数据进行处理,又将数据保存到内存,通过分页或分片技术将内存中的数据再flush至硬盘. 那JVM的内存结构到底是如何呢. JVM做为一个运行在操作系统上,但又独立于os运行的平台,它的内存至少应该包括象寄存器、堆栈等区域.

Azul开源Zing Jvm

- - InfoQ cn
4月末,继Zing 5.2 之后,. Azul Systems宣布他们将无停顿(pauseless )的 Zing JVM提供给开源软件开发者和项目,以供开发和测试. Azul Systems 工程部副总裁和合作创始人Shyam Pillalamarri向InfoQ说明道:. 我们的部署很大一部分基于开源组件,所以我们认为:“假设我们不能将一些有价值的东西免费提供给开源项目贡献者,他们将一直受限于从Java虚拟机(JVM)视角所看到的内容”,他们将不会考虑额外的用例,或者选择其他能解决了所有内存或扩展性问题、类似Zing的系统.