类加载的委托机制
类的加载一般会联系到三种类加载器(以下内容不涉及到自定义加载器):
BootstrapClassLoader
ExtendedClassLoader
AppClassLoader(又叫SystemClassLoader)
从上到下这三个加载器类是父子关系。
当运行一个程序的时候,总是由AppClassLoader开始加载指定的类,在加载类时,每个类的加载首先都会上交给父类,父类尝试加载,如果父类找不到对应的类信息,则再由子类加载。
换句话说,某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
那么什么样的累会被这三种加载器加载呢?
启动(Bootstrap)类加载器:
是用本地代码实现的类装入器,将/lib下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
标准扩展(Extension)类加载器:
是由 Sun ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将< Java_Runtime_Home >/lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
系统(System)类加载器:
是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。
委托机制具体含义
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
首先当前线程的类加载器去加载线程中的第一个类(假设为类A)。
注:当前线程的类加载器可以通过Thread类的getContextClassLoader()获得,也可以通过setContextClassLoader()自己设置类加载器。
如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器去加载类B。
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
委托机制的意义 — 防止内存中出现多份同样的字节码
比如两个类A和类B都要加载System类:
如果不用委托而是自己加载自己的,那么类A就会加载一份System字节码,然后类B又会加载一份System字节码,这样内存中就出现了两份System字节码。
如果使用委托机制,会递归的向父类查找,也就是首选用Bootstrap尝试加载,如果找不到再向下。这里的System就能在Bootstrap中找到然后加载,如果此时类B也要加载System,也从Bootstrap开始,此时Bootstrap发现已经加载过了System那么直接返回内存中的System即可而不需要重新加载,这样内存中就只有一份System的字节码了。
new一个对象(没有继承其他类的对象)的时候发生了什么
从类被加载到其对象实例化完成,总共经历了三个步骤:
加载、连接(细分为验证、准备、解析)、初始化
加载的过程主要是将类加载到方法区
紧随而来的是连接阶段,将类中的static变量(即类变量)和static方法都会被加载到方法区中的静态区,对于类属性(static修饰的属性)而言,它们的初始化也在此时确定,静态代码块 在此时执行且仅执行一次。虚拟机将为新生对象在堆上分配内存。这个任务等同于将一块确定大小的内存从java堆中划分出来。将分配到的内存空间都初始化为零值(类型对应的零值见下表),同时对对象进行必要的设置(GC年龄、对象哈希码、对应着哪个类的实例等)并将该内存区域的地址赋给栈上的变量。直到此时,虚拟机意义上的对象就创建好了。
数据类型 | 零值 | 数据类型 | 零值 |
---|---|---|---|
int | 0 | boolean | false |
long | 0L | float | 0.0f |
short | (short)0 | double | 0.0d |
char | ‘\u0000’ | reference | null |
byte | (byte)0 |
但是,这个“对象”并不能使用,因为没有进行初始化,它的所有对象属性都是零值(类属性已经在前面阶段初始化好了),实例化过程刚刚走完了2/3(或4/5)。最后,通过构造器或构造代码块进行实例初始化。
对于有父类的类是如何加载的(分层加载)
对于有父类的子类加载的过程中,首先加载父类,然后再加载子类,即所谓的分层加载。(待续)