ClassLoader浅析(一) —— Java ClassLoader

  • ClassLoader的具体作用就是将字节码格式文件加载到虚拟机中去。Java中是把class文件加载到JVM。Android中是把dex/odex文件加载入虚拟机。
  • 当JVM启动的时候,不会一下子把所有的class文件加载进JVM,而是根据需要去动态加载。

JAVA类加载

  • 在Java中有三个类加载器
    1. Bootstrap ClassLoader:启动类加载器,最顶层的加载类。负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等。他是由C++实现的,并不继承自 java.lang.ClassLoader
    2. Extention ClassLoader:扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。
    3. Application ClassLoader:应用类加载器,负责加载应用程序classpath目录下的所有jar和class文件。一般来说,Java 应用的类都是由它来完成加载的。

父加载器

  • 父加载器不是父类。 先看下AppClassLoader和ExtClassLoader的继承关系。

    image

    我们来看下ClassLoader的源码:

    public abstract class ClassLoader {
      //父加载器
      private final ClassLoader parent;
    
      private static ClassLoader scl;
    
      private ClassLoader(Void unused, ClassLoader parent) {
          this.parent = parent;
          ...
      }
        
      protected ClassLoader(ClassLoader parent) {
          this(checkCreateClassLoader(), parent);
      }
        
      protected ClassLoader() {
          this(checkCreateClassLoader(), getSystemClassLoader());
      }
        
      public final ClassLoader getParent() {
          if (parent == null)
                  return null;
          return parent;
      }
        
      public static ClassLoader getSystemClassLoader() {
          initSystemClassLoader();
          if (scl == null) {
              return null;
          }
          return scl;
      }
    
      private static synchronized void initSystemClassLoader() {
          if (!sclSet) {
              if (scl != null)
                  throw new IllegalStateException("recursive invocation");
              sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
              if (l != null) {
                  Throwable oops = null;
                  //通过Launcher获取ClassLoader
                  scl = l.getClassLoader();
                      try {
                      scl = AccessController.doPrivileged(
                          new SystemClassLoaderAction(scl));
                  } catch (PrivilegedActionException pae) {
                      oops = pae.getCause();
                      if (oops instanceof InvocationTargetException) {
                          oops = oops.getCause();
                      }
                  }
                  if (oops != null) {
                      if (oops instanceof Error) {
                          throw (Error) oops;
                      } else {
                          throw new Error(oops);
                      }
                  }
              }
              sclSet = true;
          }
      }
        ...
    }
    

    再来看看sun.misc.Launcher,它是一个java虚拟机的入口:

    public class Launcher {
        
        private static Launcher launcher = new Launcher();
        
        private static String bootClassPath = System.getProperty("sun.boot.class.path");
    
        public static Launcher getLauncher() {
            return launcher;
        }
    
        private ClassLoader loader;
    
        public Launcher() {
            ClassLoader extcl;
            try {
                //初始化ExtClassLoader
                extcl = ExtClassLoader.getExtClassLoader();
            } catch (IOException e) {
                throw new InternalError(
                    "Could not create extension class loader", e);
            }
            try {
                //初始化AppClassLoader
                loader = AppClassLoader.getAppClassLoader(extcl);
            } catch (IOException e) {
                throw new InternalError(
                    "Could not create application class loader", e);
            }
            //设置AppClassLoader为线程上下文类加载器
            Thread.currentThread().setContextClassLoader(loader);
        }
        
        public ClassLoader getClassLoader() {
            return loader;
        }
       
        static class ExtClassLoader extends URLClassLoader {
          private File[] dirs;
    
            public static ExtClassLoader getExtClassLoader() throws IOException
            {
                final File[] dirs = getExtDirs();
                return new ExtClassLoader(dirs);
            }
    
            public ExtClassLoader(File[] dirs) throws IOException {
                super(getExtURLs(dirs), null, factory);
                this.dirs = dirs;
            }
            ...
        }
    
        static class AppClassLoader extends URLClassLoader {
            public static ClassLoader getAppClassLoader(final ClassLoader extcl)
                throws IOException{
                final String s = System.getProperty("java.class.path");
                final File[] path = (s == null) ? new File[0] : getClassPath(s);
                URL[] urls = (s == null) ? new URL[0] : pathToURLs(path);
                return new AppClassLoader(urls, extcl);
            }
    
            AppClassLoader(URL[] urls, ClassLoader parent) {
                super(urls, parent, factory);
            }
            ...                                                
        }
    }
    

    从以上的源码中我们可以知道parent的赋值是在ClassLoader对象的构造方法中,它有两个情况:

    1. 由外部类创建ClassLoader时直接传入一个ClassLoader为parent。

    2. 外界不指定parent时,由getSystemClassLoader()方法生成,也就是在sun.misc.Laucher通过getClassLoader()获取,也就是AppClassLoader。直白的说,一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。

loader = AppClassLoader.getAppClassLoader(extcl);说明AppClassLoader的parent是ExtClassLoader。

但是ExtClassLoader并没有直接对parent赋值。它调用了它的父类也就是URLClassLoder的构造方法并传递了3个参数。

public  URLClassLoader(URL[] urls, ClassLoader parent,URLStreamHandlerFactory factory) {
     super(parent);
}

真相大白,ExtClassLoader的parent为null。但是实际上ExtClassLoader父类加载器是BootstrapClassLoader,我们可以从双亲委托中找到蛛丝马迹。

双亲委托

java 双亲委派.png

​ 类加载器在加载类或者其他资源时,使用的是如上图所示的双亲委派模型,这种模型要求除了顶层的BootStrap ClassLoader外,其余的类加载器都应当有自己的父类加载器,如果一个类加载器收到了类加载请求,首先会把这个请求委派给父类加载器加载,只有父类加载器无法完成类加载请求时,子类加载器才会尝试自己去加载。要理解双亲委派,可以查看ClassLoader.loadClass方法。

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 检查是否已经加载过
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            // 没有被加载过
            long t0 = System.nanoTime();
            // 首先委派给父类加载器加载
            try {
                if (parent != null) {
                     //父加载器不为空则调用父加载器的loadClass
                    c = parent.loadClass(name,false);
                } else {
                     //父加载器为空则调用Bootstrap Classloader
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // 如果父类加载器无法加载,才尝试加载
                long t1 = System.nanoTime();
                c = findClass(name);
                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

​ 在双亲委托把类加载事件一直往上传递,一直传到ExtClassLoader,由于ExtClassLoader中的parent为null而传给BootStrapClassLoader。所以说ExtClassLoader的父加载器为BootStrapClassLoader。之所以ExtClassLoader不持有BootStrapClassLoader的引用,是因为Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代d码中获取它的引用。

  • 优点:通过双亲委托可以避免重复加载和保证安全性。当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。如果我们自定义一个String来动态替换java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。

参考

深入分析Java ClassLoader原理

一看你就懂,超详细java中的ClassLoader详解

深入理解JVM之ClassLoader

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

推荐阅读更多精彩内容

  • ClassLoader翻译过来就是类加载器,普通的java开发者其实用到的不多,但对于某些框架开发者来说却非常常见...
    时待吾阅读 1,028评论 0 1
  • 1 基本信息 每个开发人员对java.lang.ClassNotFoundExcetpion这个异常肯定都不陌生,...
    java小菜鸟阅读 2,580评论 0 15
  • 作者简介 原创微信公众号郭霖 WeChat ID: guolin_blog 本篇是fank909的第四篇投稿,详细...
    木木00阅读 1,579评论 1 14
  • 麦子在大学里面做了两件引起轰动的事情:拿着水果刀追着渣男跑;在食堂面前当着所有人的面,狠狠扇了她前男友一巴掌。 这...
    叶况阅读 644评论 1 11
  • 1 类委托 Derived 的超类型列表中的 by句表示b 将会在 Derived 中内部存储。 并且编译器将成转...
    NiceDream阅读 1,489评论 1 3