类加载

类加载

         类加载是java语言提供的最强大的机制之一。尽管在日常的使用中不需要我们关注类加载的机制,但是了解其背后的机制对我们理解java虚拟机的连接模型和java语言的动态性都有很大帮助。

    在JVM中一个类的全名+它的类加载器实例是唯一的标识,不同的类加载器加载的相同类被置于不同的命名空间。所以,对同一个类被不同的类加载器实现的实例使用equles()返回的肯定是false。

类加载器

类加载器介绍

         类加载器就是寻找类的节码文件并构造出类在JVM内部表示对象的组件。JVM预置了3个类加载器:

1.  启动(Bootstrap)类加载器:引导类装入器是用本地代码实现的类装入器,它负责将 <Java_Runtime_Home>/lib 下面的类库加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

2.  标准扩展(Extension)类加载器:扩展类加载器是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader) 实现的。它负责将< Java_Runtime_Home >/lib/ext 或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。

3.  系统(System)类加载器:系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。

这3个加载器间的父子关系:

启动类加载器à扩展类加载器à系统类加载器

我们也可以定义自己的类加载器,需要实现抽象类java.lang.ClassLoader,父类加载器应该就是系统类加载器^_^。扩展类加载器和系统类加载器也都实现该抽象类(ClassLoaderàSecureClassLoaderàURLClassLoader)。

 

类加载器的双亲委派机制

         JVM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

    从源代码中可以看到,无论是扩展类加载器还是系统类加载器在加载时使用的的方法loadClass(…)都来在于类ClassLoader,通过该方法可以看到如果存在父加载器就一定会优先使用父加载器,如果没有父加载器的话会通过启动类加载器来加载,只有这2个途径都无法加载类时,才会通过自身来加载。早期的代码中通过捕获ClassNotFoundException来判别父加载器是否完成类加载,java7中通过判别类是否为null来判别。

    注意:虚拟机出于安全等因素考虑,不会加载< Java_Runtime_Home >/lib下的陌生类,开发者通过将要加载的非JDK自身的类放置到此目录下期待启动类加载器加载是不可能的。会抛出ClassNotFoundException异常的。

数组类加载器

         数组类的Class对象不是又类加载器创建的,而是由java运行时根据需要自动创建的。数组类的类加载器可由Class.getClassLoader()返回,该加载器与其元素类型的类加载器是相同的,但是如果元素类型是基本类型,则该数组没有类加载器。例:

import static java.lang.System.out;
 
public class LoaderTest {
 
    static class testClass {
    }
 
    /**
     * @param args
     */
    public static void main(String[] args) {
       testClass[] arr = new testClass[1];
       arr[0] = new testClass();
       out.println(arr.getClass().getClassLoader());
 
       int[] int_arr = { 1, 2, 3 };
       out.println(int_arr.getClass().getClassLoader());
    }
 
}


输出结果:

sun.misc.Launcher$AppClassLoader@b76fa

null

 

类加载过程

         类加载过程主要有:装载、链接、初始化。

1.        装载。查找、导入class文件

a)      通过该类型的完全限定名(包名+类名),产生一个代表该类型的二进制数据流

b)      解析这个二进制数据流为方法区的内部数据结构(方法区)

c)      创建一个表示该类型的java.lang.Class类的实例(堆上)

这样一个过程就是把一个二进制数据解析为方法区中的内部数据结构、并在堆上建立一个Class对象的过程,称为“创建创建创建创建”类型

2.        链接。执行校验、准备和解析步骤。(其中解析步骤可选)

2.1  校验。检查载入Class文件数据的正确性,以保证解析二进制数据的初始工作不会导致虚拟机崩溃。主要检查项:

a)        检查final的类不能拥有子类

b)        检查final的方法不能被覆盖

c)        确保在类型和超类型之间没有不兼容的方法声明

d)        检查final的类不能拥有子类

e)        检查final的方法不能被覆盖

f)         确保在类型和超类型之间没有不兼容的方法声明

2.2  准备。给类的静态变量分配空间。在准备阶段,Java虚拟机为类变量分配内存,设置默认初始值。但在到达初始化之前,类变量都没有被初始化为真正的初始值(准备阶段不执行Java代码)

2.3  解析。在类型的常量池中寻找类、接口、字段和方法的符号引用,把这些符号引用替换成直接引用的过程。

3.        初始化。为类变量赋予正确的初始值。对类的静态变量、静态代码块执行初始化工作。JVM实现必须在每个类或接口首次主动使用时初始化。主动使用时机为:

a)  当创建某个类的新实例时(new;或者不明确的创建。反射。克隆或者反序列化)

b)  调用某个类的静态方法

c)  使用某个类或接口的静态字段,或者对该字段赋值(final修饰的除外,它被初始化为一个编译时的常量表达式),非常量的静态字段只用在确实使用的时候才能算上是主动使用

d)  调用Java API中的某些反射方法

e)  当初始化某个类的子类时(要求超类也已经初始化)

f)  当虚拟机启动时某个被表明为启动类的类(main()方法那个类)

除了上述6种情况以外,所有其他使用Java类型的方式都是被动使用。它们都不会导致Java类型的初始化

类中声明的字段可能会被子类引用;接口中声明的字段可能会被子接口或者实现了这个接口的类引用,对于子类、子接口和实现了接口的类来说,这就是被动使用------使用它们并不会触发它们的初始化。只有当字段的确是被类或者接口声明的时候才是主动使用。

当然,超类和子类的规则对于接口并不适用,一个接口的初始化不要求它的父接口预先被初始化。只有在某个接口所声明的非常量字段被使用时,该接口才会被初始化,而不是因为这个接口的子接口或类要初始化而被初始化。

显式类加载

1.        Class可以通过forName方法来加载类,分别为

a)       forName(String className) 

b)       forName(String name, boolean initialize, ClassLoader loader) 

            其中,方法a)等效为forName(String name, true, currentLoader)currentLoader指代调用该方法的类的类加载器,所以加载时默认会使用调用类的类加载器来完成加载。 默认加载时执行初始化过程。b)方法中,如果initialize为false,就不会执行类加载过程中的初始化步骤,只有等到对类进行实例化是才会执行初始化步骤。
   
2.   ClassLoader通过方法loadClass来加载,通过前面可以知道扩展类加载器和系统类加载器也是使用的该方法来加载类。该方法等效loadClass(String name,false)
但是类加载的时候不会执行初始化步骤,所以不会执行执行类中的静态代码块什么的了。
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值