一.类装载的过程
类装载的过程分为加载、链接、初始化三个阶段,而链接又可细分为验证、准备、解析三个阶段。
1.加载
加载是装载类的第一个阶段。在该阶段首先要取得类的二进制流,然后将其转为方法区数据结构,并在Java堆中生成对应的java.lang.Class对象。
2.链接
将已经读入内存的类的二进制数据合并到jvm运行时环境中去,包括如下三个步骤:
1) 验证
验证的目的是保证Class流的格式是正确的。验证的内容包括:版本是否正确,继承的父类是否存在,是否错误的继承了final类,非抽象类是否实现了所有的抽象方法,访问的方法或字段是否存在且有足够的权限等。
2) 准备
该阶段(为类的静态变量)分配内存,并为类设置初始值,因为是类的信息,所以是在方法区中。
3) 解析
把常量池中的符号引用替换为直接引用。符号引用简单讲就是字符串,比如java.lang.object,它只是一种表示的方法,并不能直接被程序使用。要能被程序直接使用必须转为直接引用,直接引用就是指针或者地址偏移量。
3.初始化
执行类构造器<clinit>,子类的<clinit>调用前保证父类的<clinit>被调用。<clinit>是线程安全的,它主要是执行static变量的赋值语句,以及执行static{}语句。注意不包括static final类型,这种类型在更早阶段就会被赋值。
二.ClassLoader
ClassLoader类装载器,用于实现类的加载动作。ClassLoader是一个抽象类,ClassLoader的实例读入Java字节码将类装载到JVM中。ClassLoader可以定制,满足不同的字节码流获取方式。ClassLoader负责类装载过程中的加载阶段。
1.ClassLoader的种类
系统中有以下几种ClassLoader:
a.BootStrap ClassLoader (启动ClassLoader)
b.Extension ClassLoader (扩展ClassLoader)
c.App ClassLoader (应用ClassLoader/系统ClassLoader)
d.Custom ClassLoader (自定义ClassLoader)
除了BootStrap ClassLoader,每个ClassLoader都有一个Parent作为父亲,注意这不是一种继承关系。(从代码角度看实际上是聚集关系。)
2.ClassLoader的协同工作方式
a.自底向上检查类是否已加载完成
一般应用的类都在App ClassLoader加载,加载前先进行检查是否已加载,如果没有找到,App ClassLoader也不会进行加载动作,而是把请求向上给它的父类装载器Extension ClassLoader,如果Extension ClassLoader也没找到就继续将请求向上抛给BootStrap ClassLoader。
b.自顶向下尝试加载类
继续上面的例子,如果BootStrap ClassLoader也没有找到要加载的类,这时候才尝试进行加载。加载的方向是反过来的,由上向下,BootStrap ClassLoader先尝试加载,如果没有加载成功,则向下由下一级ClassLoader进行加载。
以上是ClassLoader的默认模式。注意这种模式引发一个问题,因为是向上查找类,所以下级ClassLoader可以看到上级加载的类,但上级ClassLoader看不到下级ClassLoader加载的类。
为此引入了Thread. setContextClassLoader()上下文加载器,用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题。它是一个角色,不是一种新的类装载器,任何类加载器都可以扮演这个角色。基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例。
双亲模式是默认的模式,但不是必须这么做。比如:Tomcat的WebappClassLoader 就会先加载自己的Class,找不到再委托parent。因此可以自定义ClassLoader,实现先从底层ClassLoader加载。