类加载机制
一、类的加载过程
1.1 JVM将类加载过程分为三个步骤:装载(Load),链接(Link)和初始化(Initialize)。链接又分为三个步骤,如下图所示:
1) 装载:查找并加载类的二进制数据。由类加载器完成,类加载器通常由JVM提供。
2) 链接:
验证:确保被加载类的正确性;
准备:为类的静态变量分配内存,并将其初始化为默认值;
解析:把类中二进制数据的符号引用转换为直接引用;
3) 初始化:为类的静态变量赋予正确的初始值
那为什么我要有验证这一步骤呢?首先如果由编译器生成的class文件,它肯定是符合JVM字节码格式的,但是万一有高手自己写一个class文件,让JVM加载并运行,用于恶意用途,就不妙了,因此这个class文件要先过验证这一关,不符合的话不会让它继续执行的,也是为了安全考虑吧。
1.2 类的初始化
类什么时候才被初始化:
1)创建类的实例,也就是new一个对象
2)访问某个类或接口的静态变量,或者对该静态变量赋值
3)调用类的静态方法
4)反射(Class.forName("com.lyj.load"))
5)初始化一个类的子类(会首先初始化子类的父类)
6)JVM启动时标明的启动类,即文件名和类名相同的那个类
只有这6中情况才会导致类的类的初始化。类的初始化步骤:
1)如果这个类还没有被加载和链接,那先进行加载和链接
2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
3) 假如类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。
对于一个final型的类变量,若该类变量的值在编译时就可以确定下来,那么这个类变量相当于“宏变量”。
class MyTest{//在编译时就替换成它的值,即使程序使用这个静态类变量,也不会导致类初始化 static final String compileConstant = "疯狂Java讲义"; //如果final修饰的类变量的值不能在编译时确定下来,通过类来访问类变量,会导致该类被初始化。 static final String compileConstant = System.currentTimeMillis() + ""; }
1.2 类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个这个类的java.lang.Class对象,用来封装类在方法区类的对象。看下面图
类的加载的最终产品是位于堆区中的Class对象。Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口
加载类的方式有以下几种:
1)从本地系统直接加载
2)通过网络下载.class文件
3)从zip,jar等归档文件中加载.class文件
4)从专有数据库中提取.class文件
5)将Java源文件动态编译为.class文件(服务器)
二、 JVM三种预定义类加载器
JVM预定义有三种类加载器,当一个 JVM启动的时候,Java缺省开始使用如下三种类加载器:
1)引导(根)类加载器(bootstrap class loader)
2)扩展类加载器(extensions class loader)
3)系统类加载器(system class loader)
访问JVM的类加载器
public class ClassLoaderPropTest{ public static void main(String[] args)throws IOException{ //获取系统类加载器 ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); System.out.println("系统类加载器:" + systemLoader); /* 获取系统类加载器的加载路径——通常由CLASSPATH环境变量指定 如果操作系统没有指定CLASSPATH环境变量,则默认以当前路径作为系统类加载器的加载路径 */ Enumeration<URL> em1 = systemLoader.getResources(""); while(em1.hasMoreElements()){ System.out.println(em1.nextElement()); } //获取系统类加载器的父类加载器,得到扩展类加载器 ClassLoader extensionLader = systemLoader.getParent(); System.out.println("扩展类加载器:" + extensionLader); System.out.println("扩展类加载器的加载路径:" + System.getProperty("java.ext.dirs")); System.out.println("扩展类加载器的parent:" + extensionLader.getParent()); } }
从运行结果可以看出,系统类加载器是AppClassLoader的实例,扩展类加载器是PlatformClassLoader的实例。实际上,这两个类都是URLClassLoader的实例。
JVM的根类加载器不是java实现的,程序通常无需访问根类加载器,因此访问扩展类加载器的父类加载器时返回null。
三、 类加载器“双亲委派”机制
3.1 “双亲委派”机制介绍
在这里,需要着重说明的是,JVM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。关于虚拟机默认的双亲委派机制,我们可以从系统类加载器和标准扩展类加载器为例作简单分析。
类加载器均是继承自java.lang.ClassLoader抽象类:
该方法为ClassLoader的入口点,根据指定名称来加载类,系统就是调用ClassLoader的该方法来获取指定类对应的Class对象
根据指定名称来查找类
将指定类的字节码文件(.class)读入字节数组byte []b内,并将其转换为Class对象,该字节码文件可来源于文件、网络等。
标准扩展类加载器(ExtClassLoader)和系统类加载器(AppClassLoader)以及其公共父类(java.net.URLClassLoader和java.security.SecureClassLoader)并没有覆盖java.lang.ClassLoader中默认的加载委派规则loadClass(…)方法。既然这样,我们就可以通过分析java.lang.ClassLoader中的loadClass(String name)方法的代码就可以分析出虚拟机默认采用的双亲委派机制到底是什么模样:
四、 自定义的类加载器