虚拟机的类加载机制
虚拟机的类加载机制,是一个被谈烂了的话题,估计只要学过java的人,都对其印象深刻,这里只是对这一部分的知识做一个总结、备忘。
一个Class文件要想成为能被Java虚拟机直接使用的Java类型,需要经历一系列的过程,这个过程称之为虚拟机的类加载机制,加载Class文件的模块就是类加载器。
虚拟机中类唯一性判断
Java中的类是由类加载器加载到虚拟机中的。对于任意一个类,都需要由类加载器和这个类本身来确定其唯一性,每一个类加载器,都拥有一个独立的类名称空间,不同名称空间中的类,是不可能相等的。这里所说的相等,包括代表类的Class对象的equals()方法,isAssignableFrom()方法、isInstance()方法等的返回结果
通俗来讲:同一个类ClassA,如果被两个不同的类加载器LoaderA和LoaderB加载,那么他们也不是相同的类,即使这两个类对象拥有相同的属性、方法等等
类加载器
简单来说,类加载器的主要作用就是通过一个类的全限定名来获取描述此类的二进制字节流。
从虚拟机的角度来讲,类加载器可以分为两类:
1. 启动类加载器(Bootstrap ClassLoader):这个类用C++实现,是虚拟机的一部分
2. 其他类加载器:这些类加载器是由Java语言实现,在虚拟机的外部,继承自java.lang.ClassLoader
从Java源码的角度来区分,类加载器可以分为三类:
1. 启动类加载器(Bootstrap ClassLoader
):负责将存放在$(JAVA_HOME)/lib
中,并且被虚拟机识别的类库加载到虚拟机中。该加载器无法被程序直接引用
2. 扩展类加载器(Extension ClassLoader
):负责加载${JAVA_HOME}/ext
目录下的jar包,开发者可以直接使用扩展类加载器
3. 应用程序类加载器(Application ClassLoader
):系统类加载器,负责加载ClassPath
上所指定的类库。为应用程序的默认类加载器
双亲委派模型
类加载器之间的这种层次关系,成为类加载器的双亲委派模型。
双亲委派模型要求除了顶层的启动类加载器之外,其余的加载器都应当有自己的父类加载器。==这里所说的父子关系,并不是通过继承实现的,而是通过组合==
双亲委派工作过程:如果一个类加载器收到了类的加载请求,它首先不会去尝试加载类,而是把这个请求委派给父类加载器去完成,每一层加载器都是如此。因此所有的请求最终都应该传递到顶层的启动类加载器中,只有当父类加载器无法加载类的时候,子类加载器才会尝试去进行加载
双亲委派模型其实就是一个带有优先级的树模型,在任何一个节点发起的加载请求,都必须先从根节点开始尝试加载,然后顺着根节点到当前节点的路径依次加载,知道加载成功,或者所有节点均加载失败
破坏双亲委派模型
双亲委派模型并不是Java虚拟机规范的一个强制约束模型,而是Java设计者的的一个推荐模型。Java中的大部分程序都遵循这个模型,但是也有例外。双亲委派模型并不是在所有的场合都能够满足需求
一个典型的例子是JNDI服务,JNDI服务的代码(位于rt.jar中)由启动类加载器进行加载,但是JNDI的目的确实对资源进行集中的管理和查找。它需要调用独立厂商实现并部署在应用程序CLassPath下的JNDI接口提供者,也就是Java基础类库中的代码,需要调用外部用户的代码,这点双亲委派模型没有办法做到
解决这个问题的办法是引用线程上下文类加载器(Thread Context ClassLoader)。如果线程创建的时候,还没有设置上下文加载器,那么久从父线程继承一个。如果在应用程序全局范围内都没有设置过,那么这个类加载器默认是应用程序类加载器。
线程上下文类加载器的出现,是的父类加载器可以请求子类加载器去完成类加载的动作。这种逆向的加载,其实已经打破了双亲委派模型。Java中所有涉及到SPI的加载动作,基本上都采用这种方式,例如JNDI、JDBC等等。