前言
了解 类加载器 有利用在类初始化时进行一些功能操作
本文全面讲解类加载器, 希望你们会喜欢.
在接下来的日子, 我会推出一系列讲解 JVM 的文章, 具体如下; 感兴趣可持续关注 Carson_Ho 的安卓开发笔记
示意图
目录
目录
1. 作用
实现类加载的功能
确定被加载类 在 Java 虚拟机中 的 唯一性
下面我会进行详细讲解.
1.1 实现类加载的功能
即实现 类加载过程中 "加载" 环节里 "通过类的全限定名来获取定义此类的二进制字节流" 的功能
具体请看我写的文章:(JVM)Java 虚拟机: 类加载的 5 个过程
1.2 确立 被加载类 在 Java 虚拟机中 的 唯一性
确定 两个类是否 相等 的依据: 是否由同一个类加载器加载
若 由同一个类加载器 加载, 则这两个类相等;
若 由不同的类加载器 加载, 则这两个类不相等.
即使两个类来源于同一个 Class 文件, 被同一个虚拟机加载, 这两个类都不相等
在实际使用中, 是通过下面方法的返回结果 (Boolean 值) 进行判断:
Class 对象的 equals()方法
Class 对象的 isAssignableFrom()方法
Class 对象的 isInstance()方法
当然也会使用 instanceof 关键字做对象所属关系判定等情况
实例说明
下面我将举个例子来说明:
- public class Test {
- // 自定义一个类加载器: myLoader
- // 作用: 可加载与自己在同一路径下的 Class 文件
- static ClassLoader myLoader = new ClassLoader() {
- @Override
- public Class<?> loadClass(String name) throws ClassNotFoundException {
- if (!name.equals("com.carson.Test"))
- return super.loadClass(name);
- try {
- String fileName = name.substring(name.lastIndexOf(".") + 1)
- + ".class";
- InputStream is = getClass().getResourceAsStream(fileName);
- if (is == null) {
- return super.loadClass(fileName);
- }
- byte[] b = new byte[is.available()];
- is.read(b);
- return defineClass(name, b, 0, b.length);
- } catch (IOException e) {
- throw new ClassNotFoundException(name);
- }
- }
- };
- public static void main(String[] args) throws Exception {
- Object obj = myLoader.loadClass("com.carson.Test");
- // 1. 使用该自定义类加载器加载一个名为 com.carson.Test 的类
- // 2. 实例化该对象
- System.out.println(obj);
- // 输出该对象的类 ->>第一行结果分析
- System.out.println(obj instanceof com.carson.Test);
- // 判断该对象是否属于 com.carson.Test 类 ->>第二行结果分析
- }
- }
- <-- 输出结果 -->
- class com.carson.Test
- false
- // 第一行结果分析
- // obj 对象确实是 com.carson.Test 类实例化出来的对象
- // 第二行结果分析
- // obj 对象与类 com.huachao.Test 做所属类型检查时却返回了 false
- // 原因: 虚拟机中存在了两个 Test 类(1 & 2):1 是由系统应用程序类加载器加载的, 2 是由我们自定义的类加载器加载
- // 虽然都是来自同一个 class 文件, 但由于由不同类加载器加载, 所以依然是两个独立的类
- // 做对象所属类型检查结果自然为 false.
2. 类加载器的类型
类加载器的类型数量分别从 Java 虚拟机 & Java 开发者的角度来看, 如下图
示意图
下面主要讲解从 Java 开发者角度看的类加载器, 即讲解:
启动类加载器
扩展类加载器
应用程序类加载器
2.1 启动类加载器(Bootstrap ClassLoader)
作用
负责加载以下类:
存放在 < JAVA_HOME>\lib 目录中的类
被 - Xbootclasspath 参数所指定路径中, 并且是被虚拟机识别的类库
仅按文件名识别, 如: rt.jar, 名字不符合的类库即使放在 lib 目录中也不会被加载
特别注意
启动类加载器 无法 被 Java 程序直接引用
用户在编写自定义类加载器时, 若需把 加载请求 委派 给 引导类加载器, 直接使用 null 代替即可, 如
java.lang.ClassLoader.getClassLoader()
方法所示:
- @CallerSensitive
- public ClassLoader getClassLoader() {
- ClassLoader cl = getClassLoader0();
- if (cl == null)
- return null;
- SecurityManager sm = System.getSecurityManager();
- if (sm != null) {
- ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
- }
- return cl;
- }
2.2 扩展类加载器(Extension ClassLoader)
作用:
负责加载以下类:
<JAVA_HOME>\lib\ext 目录中的类库
被 java.ext.dirs 系统变量所指定的路径中的所有类库
特别注意
由
sum.misc.Launcher$ExtClassLoader
类实现
开发者可以直接使用扩展类加载器
2.3 应用程序类加载器(Application ClassLoader)
作用:
负责加载 用户类路径 (ClassPath) 上所指定的类库
特别注意
也称为系统类加载器, 因为该类加载器是 ClassLoader 中的
getSystemClassLoader()
方法的返回值
由
sum.misc.Launcher$AppClassLoader
类实现
开发者可以直接使用该类加载器
若开发者 没 自定义类加载器, 程序默认使用该类加载器
各种类加载器的使用并不是孤立的, 而是相互配合使用
在 Java 虚拟机中, 各种类加载器 配合使用 的 模型 (关系) 是 双亲委派模型
下面我将详细讲解.
3. 双亲委派模型
3.1 模型说明
示意图
3.2 工作流程讲解
双亲委派模型的工作流程代码实现在
java.lang.ClassLoader 的 loadClass()
中
具体如下
- @Override
- protected Class<?> loadClass(String name, boolean resolve)
- throws ClassNotFoundException {
- Class<?> c = findLoadedClass(name);
- // 检查需要加载的类是否已经被加载过
- if (c == null) {
- try {
- // 若没有加载, 则调用父加载器的 loadClass()方法
- if (parent != null) {
- c = parent.loadClass(name, false);
- }else{
- // 若父类加载器为空, 则默认使用启动类加载器作为父加载器
- c=findBootstrapClassOrNull(name);
- }
- } catch (ClassNotFoundException e) {
- // 若父类加载器加载失败会抛出 ClassNotFoundException,
- // 说明父类加载器无法完成加载请求
- }
- if(c==null){
- // 在父类加载器无法加载时
- // 再调用本身的 findClass 方法进行类加载
- c=findClass(name);
- }
- }
- if(resolve){
- resolveClass(c);
- }
- return c;
- }
步骤总结: 若一个类加载器收到了类加载请求
把 该类加载请求 委派给 父类加载器去完成, 而不会自己去加载该类
每层的类加载器都是如此, 因此所有的加载请求最终都应传送到顶层的启动类加载器中
只有当 父类加载器 反馈 自己无法完成该加载请求 (它的搜索范围中没有找到所需的类) 时, 子加载器才会自己去加载
3.3 优点
Java 类随着它的类加载器一起具备了一种带优先级的层次关系
如: 类 java.lang.Object(存放在 rt.jar 中)在加载过程中, 无论哪一个类加载器要加载这个类, 最终需委派给模型顶端的启动类加载器进行加载, 因此 Object 类在程序的各种类加载器环境中都是同一个类.
若没有使用双亲委派模型(即由各个类加载器自行去加载), 用户编写了一个 java.lang.Object 的类(放在 ClassPath 中), 那系统中将出现多个不同的 Object 类, Java 体系中最基础的行为就无法保证
在讲完系统的类加载器后, 下面我将讲解如何根据需求自定义类加载器.
4. 自定义类加载器
主要是通过继承自 ClassLoader 类 从而自定义一个类加载器
MyClassLoader.java
- // 继承自 ClassLoader 类
- public class MyClassLoader extends ClassLoader {
- // 类加载器的名称
- private String name;
- // 类存放的路径
- private String classpath = "E:/";
- MyClassLoader(String name) {
- this.name = name;
- }
- MyClassLoader(ClassLoader parent, String name) {
- super(parent);
- this.name = name;
- }
- @Override
- public Class<?> findClass(String name) {
- byte[] data = loadClassData(name);
- return this.defineClass(name, data, 0, data.length);
- }
- public byte[] loadClassData(String name) {
- try {
- name = name.replace(".", "//");
- System.out.println(name);
- FileInputStream is = new FileInputStream(new File(classpath + name
- + ".class"));
- byte[] data = new byte[is.available()];
- is.read(data);
- is.close();
- return data;
- } catch (Exception e) {
- e.printStackTrace();
- }
- return null;
- }
- }
下面我将用一个实例来说明如何自定义类加载器 & 使用.
步骤 1: 自定义类加载器 MyClassLoader
MyClassLoader.java
- // 继承自 ClassLoader 类
- public class MyClassLoader extends ClassLoader {
- // 类加载器的名称
- private String name;
- // 类存放的路径
- private String classpath = "E:/";
- MyClassLoader(String name) {
- this.name = name;
- }
- MyClassLoader(ClassLoader parent, String name) {
- super(parent);
- this.name = name;
- }
- @Override
- public Class<?> findClass(String name) {
- byte[] data = loadClassData(name);
- return this.defineClass(name, data, 0, data.length);
- }
- public byte[] loadClassData(String name) {
- try {
- name = name.replace(".", "//");
- System.out.println(name);
- FileInputStream is = new FileInputStream(new File(classpath + name
- + ".class"));
- byte[] data = new byte[is.available()];
- is.read(data);
- is.close();
- return data;
- } catch (Exception e) {
- e.printStackTrace();
- }
- return null;
- }
- }
步骤 2: 定义待加载的类
TestObject.java
- public class TestObject {
- public void print() {
- System.out.println("hello DiyClassLoader");
- }
- }
步骤 3: 定义测试类
Test.java
- public class Test {
- public static void main(String[] args) throws InstantiationException,
- IllegalAccessException, ClassNotFoundException {
- MyClassLoader cl = new MyClassLoader("myClassLoader");
- // 步骤 1: 创建自定义类加载器对象
- Class<?> clazz = cl.loadClass("com.carson.TestObject");
- // 步骤 2: 加载定义的测试类: myClassLoader 类
- TestObject test= (TestObject) clazz.newInstance();
- // 步骤 3: 获得该类的对象
- test.print();
- // 输出
- }
- }
- // 输出结果
- hello DiyClassLoader
4. 总结
本文全面讲解类加载器
在接下来的日子, 我会推出一系列讲解 JVM 的文章, 具体如下; 感兴趣可持续关注 Carson_Ho 的安卓开发笔记
示意图
请点赞! 因为你的鼓励是我写作的最大动力!
来源: http://www.jianshu.com/p/8743d8062bb6