类的生命周期:
一个类型从被加载到虚拟内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resoulution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称为连接(Linking)。这七个阶段发生的顺序如图所示。
类的生命周期
类的加载过程
加载:“加载”(Loading)阶段是 整个“类加载(Class Loading)”过程的第一个阶段,不能混淆这两个看起来相似的名词。在加载阶段,Java虚拟机需要完成以下三件事情:
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口。
验证:验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
准备:准备阶段是正式为类中定义常量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段,从概念上讲,这些变量所使用的内存都应当在方法区中进行分配。关于准备阶段,还有两个容易产生混淆的概念,首先是这时候进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。其次是这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:
public static int value = 123:
那变量value在准备阶段过后的初始值为0而不是123。
解析:解析阶段是java虚拟机将常量池内的符号引用替换为直接引用的过程。
初始化:类的初始化阶段是类加载的最后一个步骤,之前介绍的几个类加载的动作里,除了在加载阶段用户应用程序可以通过自定义类加载器的方式局部参与外,其余动作都完全由Java虚拟机来主导控制。直到初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给 应用程序。
三层类加载器
启动类加载器(Bootstrap Class Loader):这个类加载器负责加载存放在<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够识别的类库加载到虚拟机的内存中。
扩展类加载器(Extension Class Loader):这个类加载器是在类sun.misc.Launcher$ExtClassLoader中以Java代码的形式实现的。它负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中的所有类库。
应用程序类加载器(Application Class Loader):这个类加载器由sun.misc.Launcher$ClassLoader来实现。由于应用程序类加载器是ClassLoader类中的getSystem-ClassLoader()方法的返回值,所以有些场合 中也称为它的”系统类加载器“。它负责加载用户路径(ClassPath)上的所有类库开发者同样可以直接在代码中使用这个类加载器。
双亲委派模型
上图展示的各类加载器之间的层次刮泥被成为类加载器的”双亲委派模型(Parents Delegation Model)“。双亲委派模型要求除了顶层的启动加载器外,其余的类加载器都应有自己的父类加载器。不过这里类加载器之间的父子关系一般不是以继承的关系来实现的,而是通常使用组合关系来复用父加载器的代码。
双亲委派模型的工作过程是:如果一个类加载器受到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需要的类)时,子加载器才会尝试自己去完成加载。
使用双亲委派模型来组织类加载器之间的关系,一个显而易见的好处就是Java中类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object在程序的各种类加载器环境中都能够保证是同一个类。反之,如果没有使用双亲委派模型,都由各个类加载器自行去加载的话,如果用户自己也编写一个名为java.lang.Object的中类,并放在程序的Classpath中,那系统中就会出现多个不同的Object类,Java类型体系中最基础的行为就无从保证,应用程序将会变得一片混乱。
双亲委派模型对于保证Java程序的运作极为重要,但它的实现却很简单,用以实现双亲委派的代码只有短短十余行,全部集中在java.lang.ClassLoder的loadClass()方法之中,如代码清单:
protected synchroized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
//首先,检查请求的类是否已经被加载过了
Class c = findLoadedClass(name);
if(c == null){
try{
if(parent !=null){
c = parent,loadClass(name,false);
}
else{
c = findBootstrapClassOrNull(name);
}catch(ClassNotFoundException e){
//说明父类加载器无法加载时
//再调用本身的findClass方法来进行类加载
c = fingClass(name);
}
}
if(resolve){
resolveClass(c)
}
return c;
}
}
这段代码还是比较清晰易懂的:先检查请求加载器的类型是否已经被加载过,若没有则调用父加载器的loadClass()方法,若父类加载器为空则默认使用启动类加载器作为父加载器。假如父类加载器加载失败,抛出ClassNotFoundException异常的话,才调用自己的findClass()方法尝试加载。