虚拟机类加载机制

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

类加载的时机

从加载到虚拟机内存中开始,到卸载出内存为止,整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载,其中验证、准备、解析3个部分称为连接。

加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始(注意是开始,这些阶段通常是相互交叉混合式进行),而解析阶段则不一定:在某些情况下可以在初始化阶段之后在开始,这是为了支持Java语言的运行时绑定。

类与类加载器

类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用远远不限于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间,比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那两个类就必定是不相等的。

双亲委派模型

从虚拟机角度讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),另一种是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。

从Java开发人员角度来看,类加载器还可以划分得更细致一些。

启动类加载器(Bootstrap ClassLoader):这个类加载器负责将存放在<JAVA_HOME>lib目录中的,或者被-Xrootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录下也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,那么直接使用null代替即可。

扩展类加载器(Extension ClassLoader):这个类加载器负责加载<JAVA_HOME>libext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。

应用程序加载器(Application ClassLoader):这个类加载器是ClassLoader中getSystemClassLoader()方法的返回值,所以一般称为系统类加载器,负责加载用户类路径上所指定的类库。

工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己尝试去加载这个类,而是把这个请求委派给父类加载器去完成,每一层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

好处是:Java类随着它的类加载器一起具备了一种带有优先级的层次关系,例如类java.lang.Object,它存放在rt.jar中,无论哪一个类加载器去加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。实现简单,代码集中在java.lang.ClassLoader的loadClass()方法之中先检查是否被加载过,若没有加载则调用父加载器的loadClass()方法,若父类加载器为空则默认使用启动类加载器作为父加载器,如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。

破坏双亲委派模型

JNDI服务的代码由启动类加载器去加载,但JNDI的目的是对资源进行集中管理和查找,需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口的提供者的代码,但启动类加载器不知道这些代码,为了解决这个问题,提供线程上下文类加载器,这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,将从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那么这个类加载器默认就是应用程序类加载器。有了线程上下文类加载器,JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作。

原文地址:https://www.cnblogs.com/13jhzeng/p/5897311.html