一 定义
JVM设计团队把类加载过程中的“通过一个类的全限定名获取到描述此类的二进制流”这个动作放到Java虚拟机外部实现,以便让应用程序自己决定如何获取类。实现这个动作的功能模块,叫类加载器。
二 类与类加载器
对于任意一个类,都需要由加载他的类加载器和这个类本身一起确立其在Java虚拟机中的唯一性,每一个类加载器,都有一个独立的类名称空间。
所以,使用不同的类加载器,去加载同一个Class文件表示的类,会得到两个不同的类。(使用instanceof判断时会返回false)
三 类加载器的分类
1 启动类加载器(Bootstrap ClassLoader)
负责将存放在<JAVA_HOME>/lib 目录中的、或者被-Xbootclasspath参数所指定的目录中的、并且虚拟机可识别的类加载到JVM内存中。
开发者不可直接调用。
2 扩展类加载器(Extension ClassLoader)
负责将<JAVA_HOME>/lib/ext 目录中,或者被java.ext.dirs系统变量所指定的路径中的类,加载到JVM内存中。
开发者可以直接调用。
3 应用程序类加载器(Application ClassLoader)
也叫系统类加载器,负责加载用户类路径(ClassPath)上的类。
开发者可以直接调用,如果应用程序中没有自定义过自己的类加载器,默认使用应用程序类加载器。
四 双亲委派模型
1 双亲委派模型的结构
双亲委派模型的中,除了顶层加载器,其他的加载器都应该有自己的父加载器。
这种父子关系不是用继承来实现的,而是用组合来实现的。(父类是子类的成员)
最顶层是启动类加载器,往下是扩展类加载器,往下是应用程序类加载器(系统加载器),再往下才是自定义加载器。
2 工作流程
双亲委派模型不是必须遵守的,但确实JVM团队推荐的一种类加载器组织方式。其工作流程如下:
当一个类加载器收到类加载请求,首先将这个请求委派给父类完成,每一层次的类加载器都是如此,因此所有加载请求最终都发送给了启动类加载器。只有当父类加载器反馈自己无法完成对这个类的加载时,子类才会尝试去加载类。这样就保证了“越基础的类由越上层的类加载器完成加载”。
3 存在的问题
在为一个类选择类加载器时,通常使用引用当前类的类所使用的类加载器完成类的加载。在双亲委派模型中,越是基本的类越使用上层的类加载器来加载。如果比较基本的类中引用了“不是很基本的类”(一般是用户的类),那样就无法完成类的加载。
最常见的例子就是Java的JNDI服务,JNDI的代码需要使用启动类加载器加载,那么它引用的类也只能使用启动类加载器完成。但是在JNDI必须要调用独立厂商实现的、部署在ClassPath下的SPI代码(Service Provider Interface)。启动类加载器肯定无法加载这些代码。
4 解决方法
为了解决4中的问题,Java团队设计出了一个违背双亲委派模型的类加载器——线程上下文类加载器。
这个类加载器通过java.lang.Thread类的setContextClassLoaser()方法设置。如果线程没有设置,那么它会从其父线程中继承。如果在全局范围内都没有设置的话,默认使用应用程序类加载器。
五 类加载器的基本机制
1 委派
即双亲委派模型
2 可见性
在双亲委派模型中,子类加载器可以看到父加载器加载的模型,而父加载器看不到子加载器加载的模型。
3 单一性
在双亲委派模型中,一个类仅被加载一次。