Carson带你学JVM:你真的了解类加载器吗?(含双亲委派模型)

前言

  • 了解 类加载器 有利用在类初始化时进行一些功能操作
  • 本文全面讲解类加载器,希望你们会喜欢。

Carson带你学JVM系列文章,具体如下:
Carson带你学JVM:这是一份全面 & 详细的JVM学习指南
Carson带你学JVM:图文解析Java虚拟机内存结构
Carson带你学JVM:Java对象的创建、内存布局 & 访问定位全过程解析
Carson带你学JVM:Java对象如何判断存活原则-引用计数法 & 引用链法
Carson带你学JVM:这是一份全面 & 详细的垃圾收集算法(GC)讲解攻略
Carson带你学JVM:常见的垃圾收集器学习指南
Carson带你学JVM:类加载的全过程解析
Carson带你学JVM:你真的了解类加载器吗?(含双亲委派模型)
Carson带你学JVM:方法分派模型-静态分派、动态分派


目录

目录

1. 作用

  1. 实现类加载的功能
  2. 确定被加载类 在 Java虚拟机中 的 唯一性

下面我会进行详细讲解。

1.1 实现类加载的功能

即实现 类加载过程中“加载”环节里 “通过类的全限定名来获取定义此类的二进制字节流” 的功能

具体请看我写的文章:(JVM)Java虚拟机:类加载的5个过程

1.2 确立 被加载类 在 Java虚拟机中 的 唯一性

  • 确定 两个类是否 相等 的依据:是否由同一个类加载器加载
    1. 若 由同一个类加载器 加载,则这两个类相等;
    2. 若 由不同的类加载器 加载,则这两个类不相等。

即使两个类来源于同一个 Class 文件、被同一个虚拟机加载,这两个类都不相等

  • 在实际使用中,是通过下面方法的返回结果(Boolean值)进行判断:
    1. Class对象的equals()方法
    2. Class对象的isAssignableFrom()方法
    3. 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 开发者角度看的类加载器,即讲解:
    1. 启动类加载器
    2. 扩展类加载器
    3. 应用程序类加载器

2.1 启动类加载器(Bootstrap ClassLoader)

  • 作用
    负责加载以下类:
    1. 存放在<JAVA_HOME>\lib目录中的类
    2. -Xbootclasspath参数所指定路径中、并且是被虚拟机识别的类库

仅按文件名识别,如:rt.jar,名字不符合的类库即使放在lib目录中也不会被加载

  • 特别注意
    1. 启动类加载器 无法 被Java程序直接引用
    2. 用户在编写自定义类加载器时,若需把 加载请求 委派 给 引导类加载器,直接使用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)

  • 作用:
    负责加载以下类:

    1. <JAVA_HOME>\lib\ext目录中的类库
    2. java.ext.dirs系统变量所指定的路径中的所有类库
  • 特别注意

    1. sum.misc.Launcher$ExtClassLoader类实现
    2. 开发者可以直接使用扩展类加载器

2.3 应用程序类加载器(Application ClassLoader)

  • 作用:
    负责加载 用户类路径(ClassPath)上所指定的类库

  • 特别注意

    1. 也称为系统类加载器,因为该类加载器是ClassLoader中的getSystemClassLoader()方法的返回值
    2. sum.misc.Launcher$AppClassLoader类实现
    3. 开发者可以直接使用该类加载器
    4. 若开发者 没 自定义类加载器,程序默认使用该类加载器

  • 各种类加载器的使用并不是孤立的,而是相互配合使用
  • 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; 
}

步骤总结:若一个类加载器收到了类加载请求

  1. 把 该类加载请求 委派给 父类加载器去完成,而不会自己去加载该类

每层的类加载器都是如此,因此所有的加载请求最终都应传送到顶层的启动类加载器中

  1. 只有当 父类加载器 反馈 自己无法完成该加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会自己去加载

3.3 优点

Java类随着它的类加载器一起具备了一种带优先级的层次关系

  1. 如:类 java.lang.Object(存放在rt.jar中)在加载过程中,无论哪一个类加载器要加载这个类,最终需委派给模型顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。
  2. 若没有使用双亲委派模型(即由各个类加载器自行去加载)、用户编写了一个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. 总结

  • 本文全面讲解类加载器
  • 接下来我会对Java虚拟机(JVM)进行详细的分析,欢迎关注Carson_Ho的简书,不定期分享关于安卓开发的干货,追求短、平、快,但却不缺深度

请点赞!因为你的鼓励是我写作的最大动力!

Carson带你学JVM系列文章,具体如下:
Carson带你学JVM:这是一份全面 & 详细的JVM学习指南
Carson带你学JVM:图文解析Java虚拟机内存结构
Carson带你学JVM:Java对象的创建、内存布局 & 访问定位全过程解析
Carson带你学JVM:Java对象如何判断存活原则-引用计数法 & 引用链法
Carson带你学JVM:这是一份全面 & 详细的垃圾收集算法(GC)讲解攻略
Carson带你学JVM:常见的垃圾收集器学习指南
Carson带你学JVM:类加载的全过程解析
Carson带你学JVM:你真的了解类加载器吗?(含双亲委派模型)
Carson带你学JVM:方法分派模型-静态分派、动态分派

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容