Java类加载器

本文和大家聊聊Java类加载器这档子事。

什么是类加载器?

咱们先来给他下一个通俗点的定义:

将java字节码(.class文件)转换成类对象(java.lang.Class),加载到JVM内存。

Java中有哪几种类加载器?

类加载器可分为四种:
Bootstrap ClassLoader:由C++代码实现,加载sun.boot.class.path所指定的目录,或<JAVA_HOME>\lib目录中文件。

Ext ClassLoader:加载系统变量 java.ext.dirs所指定的目录,或<JAVA_HOME>\lib\ext 目录中的文件。

App ClassLoader:加载用户类路径下的文件。

Custom ClassLoader:开发者自己折腾的类加载器,按需进行类文件加载。(例如:网络类加载器,通过网络加载.class文件)

类加载器如何工作?

第一步,类加载器向JVM查询目标对象(请求加载的对象)是否已经加载过,若已经加载过,则直接返回此对象;否则,进行第二步。

第二步,获得当前类加载器的父类加载器,递归执行这一步骤,由下往上,直至到达顶级父类加载器(即Bootstrap ClassLoader),进行第三步。

第三步,从顶级父类加载器,由上而下,在自己的搜索范围内检索目标对象的类文件(.class),若检索到,则读取该文件的字节码转换成Class对象到JVM,返回目标对象;否则,抛出ClassNotFoundException。

有读者老爷说了,听你白扯了半天,对类加载器的认知还是不清楚,有没有实质性的东西?下面咱们来点实的。

类加载器的委派模式

从上文中类加载器的工作步骤,可以看出,递归获取父类加载器,由下往上,直达顶级父类加载器,在从顶级父类加载器,自上而下,逐级在自己的搜索范围内检索目标对象类文件,转换成Class对象(若检索到)的这种行为,称为类加载器的委派模式

描述递归获取父类加载器,由下往上,直达顶级父类加载器的代码如下:

//代码段截取自ClassLoader.loadClass(String name, boolean resolve)
 if (c == null) {
        long t0 = System.nanoTime();
        try {
              if (parent != null) {
                  c = parent.loadClass(name, false);//递归获取父类加载器
              } else {
                  c = findBootstrapClassOrNull(name);//顶级父类加载器
              }
           } catch (ClassNotFoundException e) {
                  // ClassNotFoundException thrown if class not found
                  // from the non-null parent class loader
           }

描述从顶级父类加载器,自上而下,逐级在自己的搜索范围内检索目标对象类文件,转换成Class对象的代码如下:

 //代码段截取自ClassLoader.loadClass(String name, boolean resolve)
   if (c == null) {
      // If still not found, then invoke findClass in order
      // to find the class.
      long t1 = System.nanoTime();
      c = findClass(name);//在自己的搜索范围内进行检索,若找到,转换成Class对象
       .....................................
       .....................................
      }
      if (resolve) {
            resolveClass(c);
       }
       return c;

真正实现类加载的是defineClass方法,代码如下:

//代码段截取自URLClassLoader.findClass(final String name)
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
   try {
          return defineClass(name, res); //执行类加载工作
   } catch (IOException e) {
         throw new ClassNotFoundException(name, e);
   }
} else {
       return null;
}

回顾类加载器的委派模式,我们发现,发起类加载请求的类加载器,不一定会最终执行类的加载操作,可能其父类加载器来执行类加载。

发起类加载请求的类加载器称为初始类加载器;完成类加载操作的类加载器称为定义类加载器。(为啥叫定义类加载?因名而得呗-defineClass)

有读者老爷发问了,为什么要设计这种委派模式

委派模式的作用

个人理解,设计这种委派模式的用意有两点:
第一,避免对象的重复加载
第二,保护Java基础类型的行为

关于第一点,自然不必多说。
对于第二点,咱们设想一下,在没有委派模式的场景中,用户自己编写了一个java.lang.String类,并将其放置在程式的类路径(ClassPath)中,那系统中就存在了多个不同的String类,这使得Java的基础类型的行为无法得到保证,程式也会变得很混乱。

细心的读者可能会从上面的例子中发现,同一个类被两个不同的类加载器加载会出现问题,产生这种问题的原因是什么?在后面的类加载器的隔离性中会进行解释

类加载器的层级结构是怎样的?

如下是类加载器层级示意图:


口说无凭,咱们来验证一下类加载器的层级结构,执行如下代码:

 package com.hys;

 public class Main {

     public static void main(String[] args) {
 
         ClassLoader c1 = User.class.getClassLoader();
         System.out.println(c1);
 
          ClassLoader c2 = c1.getParent();
          System.out.println(c2);         

          ClassLoader c3 = c2.getParent(); 
          System.out.println(c3); 
    }
}

执行结果:

sun.misc.Launcher$AppClassLoader@b4aac2
sun.misc.Launcher$ExtClassLoader@140e19d
null 

Process finished with exit code 0

执行结果符合咱们的预期。

有读者老爷发问了,怎么就符合预期了?结果中有个值为null,是个啥?Bootstrap ClassLoader呢?
原因在于顶级类加载器是Bootstrap ClassLoader,此加载器是C++代码编写,所以,使用Java获取的时候,返回了null

类加载器的隔离性

JVM如何判断一个类的唯一性?
JVM通过加载该类的类加载器和类名称来确定一个类的唯一性。

这意味着我们使用两个不同的类加载器加载同一个类,而在JVM层面上却认为这是两个完全不同的类,从另一个层面看,相当于类被类加载器所包裹与另个类加载器中的类进行了隔离

如何证明这种隔离的存在?执行如下代码:

package com.hys;

public class Main {
    public static void main(String[] args) {

        try{
           User u1 = new User();
           User u2 = new User();
           // u1和u2都是同一个类加载器加载的
           System.out.println("The same classloader: u1.isInstance(u2)" + u1.getClass().isInstance(u2));

            MyClassLoader c1 = new MyClassLoader();
            Class clazz = c1.loadClass("com.hys.User");
           // clazz是MyClassLoader类加载器加载的
            System.out.println("The different classloader: clazz.isInstance(u1)" + clazz.isInstance(u1));
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }
}

执行结果:

The same classloader: u1.isInstance(u2)true
The different classloader: clazz.isInstance(u1)false

Process finished with exit code 0

自定义类加载器

自定义类加载器,一般是继承ClassLoader类,覆盖findClass(String name)方法,在此方法中做查询与读取.class文件的操作,如下代码:

package com.hys;

import java.io.*;

public class MyClassLoader extends ClassLoader {

    private String mRootDir;

    public MyClassLoader() {

    }

    public MyClassLoader(String rootDir) {
        mRootDir = rootDir;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data = loadClassData(name);
        if (data == null) {
            throw new ClassNotFoundException();
        }else{
            return defineClass(name, data, 0, data.length);
        }
    }

    private byte[]loadClassData(String className) {
        String path = classNameToPath(className);
        try {
            InputStream ins = new FileInputStream(path);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesNumRead = 0;
            while ((bytesNumRead = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesNumRead);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private String classNameToPath(String className) {

        String classPath;
        if(mRootDir == null || mRootDir.isEmpty())
            classPath = className.replace('.', File.separatorChar) + ".class";
        else
            classPath = mRootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class";

        return classPath;
    }
}

我是青岚之峰,如果读完后觉的有所收获,欢迎点赞加关注

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

推荐阅读更多精彩内容