类加载过程
加载
查找并加载类的二进制数据。在该阶段虚拟机需要完成3件事情:
1.通过一个类的全限定名来获取类定义此类的二进制流。
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3.在内存(堆区)中生成一个代表这个类的java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。
连接
连接是将已经读入内存的类的二进制数据合并到虚拟机的运行时环境中去。
验证
确保被加载的类的正确性。验证过程主要分为4个阶段分别为:文件格式校验、元数据校验,字节码校验、符号引用验证。
准备
为类的静态变量分配内存,并将赋予默认值,例如:该阶段value的初始值为0,该阶段为value赋值为0,而不是123。
1 | public static int value=123; |
解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。该阶段可能发生在初始化之前也可能发生在初始化阶段之后。
初始化
为类的静态变量赋予正确的初始值。如上例子中:该阶段才完成value赋值为123。
类初始化时机有且只有以下五种情况:
1.遇到new (新建对象时),getstatic(读取静态变量时),putstatic(写一个的静态变量)或invokestatic(调用静态方法时)这4条字节码指令时。
2.使用java.lang.reflect 包的方法对类进行反射调用的时候。
3.当初始化一个类的时候,如果发现父类还没有初始化,则先需要先触发器父类的初始化。
4.当虚拟机启动时,用户需要制定一个执行的主类(main 方法所在类),虚拟机会先初始化这个类。
5、动态语言支持中,如果一个java.lang.invoke.MethodHandler实例返回的戒指结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
类加载器的分类
从Java虚拟机的角度来讲,只存在两种不同的类加载器: 一种是启动类加载器,这个类加载器使用C++语言实现,是虚拟机的一部分;另一种就是所有的其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全部都技能自抽象类java.lang,ClassLoader。
java虚拟机自带的加载器
启动类加载器(Bootstrap)
这个加载器负责将<JAVA_HOMT>\lib 目录中的,或者被-Xbootclasspath参数所指定的路径中的并且是虚拟机识别的类库加载到虚拟机内存中。程序员无法再java代码中获取该类。
扩展类加载器(Extension)
j这个加载器由sun.misc.Launcher$ExtClassLoader实现,负责加载<JAVA_HOME>\lib\ext 目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器
应用加载器(System)
这个加载器有sun.misc.Launcher$App-ClassLoader实现。由于该类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也成为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库。所以开发者可以直接使用这个类加载器,如果应用程序中没有自定义自己的类加载器,一般情况下这个就是程序中默认的类加载器。
用户自定义的类加载器
用户自定义类加载器,必须是java.lang.ClassLoader的子类,可以定义类的加载行为。
双亲委派模型
双亲委派模型要求除了顶层的启动类加载外,其余的类加载器都应当有自己的父类加载器,这里类加载器的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Cpmposition)关系来复用附加在其的代码 。
双亲委派模型的工作过程
如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个请求时,子加载器才会去尝试自己去加载。双亲委派模型的好处是:能够有效确保一个类的全局唯一性。