Java可以动态扩展的语言特性就是依赖运行期间动态加载和动态链接来实现的。
1. 在实际情况中,每个Class文件都有可能代表着Java语言中的一个类或接口,后文中直接对“类”的描述包括了类和接口的可能性,而对于类和接口要分开描述的场景会特别指明;
2. 所提到的“Class文件”并非特指某个存于具体磁盘中的文件,应当是一串二进制的字节流,无论以何种形式存在都可以。
类从被加载到虚拟机内存中开始,到卸载出内存为止,完整生命周期有7个阶段:
加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)。
顺序如下图:其中验证、准备、解析统称为连接(Linking)
其中加载、验证、准备、初始化和卸载着5个阶段的顺序是确定的,类的加载过程都是以这个顺序开始。
规范中规定有且只有5种情况必须立即执行类的“初始化”(之前已经做了加载、验证、准备):
1)遇到new、getstatic、putstatic或invokestatic这4个字节码指令;
2)使用java.lang.reflect包的方法对类进行反射调用的时候;
3)初始化一个类的时候,如果发现其父类还没有进行过初始化,现对其父类出发类的初始化;
4)VM(虚拟机)启动的时候,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类;
5)动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄对应的类没有进行过初始化,需要出发初始化。
类加载的全过程包括加载、验证、准备、解析、初始化5个部分,分清类加载和加载的概念,加载是类加载的一个阶段。
加载阶段完成的3件事:
1)通过一个类的全限定名来获取描述定义此类的二进制字节流;(可以理解为 想从哪里获取就从哪里获取)
2)将该字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区的这个类的各种数据的访问入口。
连接阶段的第一步,目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,且不会危害VM自身安全。具体说明《Java虚拟机规范》。
大致完成4个阶段的检验动作:文本格式验证、元数据验证、字节码验证、符号引用验证。
正式为类变量分配内存并设置类变量初始值的阶段,变量所用的内存都将在方法区中进行分配。
这里的变量仅包括类变量(被static修饰的变量),不包括实例变量,实例变量会在对象实例化的时候随对象一起分配在堆中。初始值一般是数据类型的零值。
该阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用(Symbolic Reference):以一组符号来描述所引用的目标,可以是任何形式的字面量,只要符合规范即可。
直接引用(Direct Reference):可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。
解析的具体类容推荐《深入理解Java虚拟机》中所述。
类加载过程的最后一步,前面的几个过程中出了在 加载 阶段可以自定义类加载器外,其余动作完全有虚拟机主导和控制。到初始化才整整开始执行类中的Java程序代码。
加载阶段通过一个类的全限定名来获取描述定义此类的二进制字节流,该操作在Java虚拟机外部实现,完成该操作的代码块称为“类加载器”。
对于任意一个类,需要有加载它的类加载器和这个类本身一同确定其在Java虚拟机中的唯一性,每一个类加载器都有一个独立的类命名空间。
即比较两个类是否“相等”,只有在这两个类都是同一个类加载器加载的前提下才有意义。“相等”包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果。
从JVM角度,只存在两种类加载器:
1)启动类加载器(Bootstrap ClassLoader),由C++实现,是JVM的一部分;
2)所有其他的类加载器,由Java语言实现,独立于JVM外部,且全部继承自抽象类java.lang.ClassLoader。
从开发人员角度,
1)启动类加载器,负责加载放在JAVA_HOME\lib目录中,或被-Cbootclasspath参数所指定的路径中,由JVM识别的类库加载到虚拟机内存中;
2)扩展类加载器(Extension ClassLoader),由sun.misc.Launcher$ExtClassLoader实现,负责加载JAVA_HOME\lib\ext目录中或是java.ext.dirs系统变量所制定的路径中的类库,开发人员直接使用扩展类加载器;
3)应用程序类加载器(Application ClassLoader),由sun.misc.Launcher$AppClassLoader实现,由于该类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称为系统类加载器。负责加载用户类路径(classpath)所制定的类库,开发者可以直接使用,如果应用程序没有自定义自己的类加载器,一般情况下这就是程序中默认的类加载器。
一般配合以上3个使用,如果没有必要,一般不会自定义自己的类加载器。
类加载器之间的关系如下图:该层次关系成为类加载器的双亲委派模型(Parents Delegation Model)
在双亲委派模型中,除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。一般不是继承关系实现,而是组合关系来复用父类加载器的代码。
工作过程:
如果一个类加载器收到了类加载的请求,首先不会自己尝试加载这个类,而是委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个家在请求(它的搜索范围内没有找到所需要的类)时,子加载器才会尝试自己去加载。
Java类随着它的类加载器一起具备了一种带有优先级的层次关系。
该模型不是强制性的约束模型,而是Java设计者推荐给开发者的类加载器实现方式。Java中大部分类加载器遵循这个模型,也有例外,比如3次较大规模的“被破坏”情况。
1. JDK1.2发布之前。
2. 由模型自身的缺陷导致,该模型很好的解决了各个类加载器的基础类的统一问题(越基础的类由越上层的加载器进行加载)。线程上下文类加载器(Thread Context ClassLoader),解决基础类调用回用户代码的问题,如JNDI服务。
3. 由于用户对程序动态性的追求所导致。动态性:代码热替换(HotSwap)、模块热部署(HotDeployment),像外设一样不用重启就可以使用。
原文:https://www.cnblogs.com/baishouzu/p/12332251.html