吃透JVM篇(3)-jvm的classLoader和android的classLoadder

目录

吃透JVM篇(1)-JVM包含什么,如何运行的码
吃透JVM篇(2)-class字节码里都是啥
吃透JVM篇(3)-jvm的classLoader和android的classLoadder

看了这篇文章可能能知道什么?

1,类加载器都干了什么?

2,jvm中都包含哪些类加载器

3,android中包含哪些类加载器

为什么要看类加载器?

因为类加载器的内容涉及到热更新,插件化相关的基础,了解类加载器逻辑后,可以更加快速的了解热更新和插件化的原理

开始研究类加载器

类加载器什么时候加载类?类加载器都干了什么?

什么时候加载

当执行到以下五种情况的时候会加载类
1、使用new字节码指令创建类的实例,或者使用getstatic、putstatic读取或设置一个静态字段的值(放入常量池中的常量除外),或者调用一个静态方法的时候时。

2、通过java.lang.reflect包的方法对类进行反射调用的时候,如果类没有加载时。

3、当初始化一个类的时候,如果发现其父类没有加载,则首先触发父类初始化。

4、当虚拟机启动时,用户需要指定一个主类(包含main()方法的类)时。

5、使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行初始化时。

都干了什么

类加载器主要就是将code码读到机器内存中执行
虽然上面一句话说的很简单,但是在jvm里处理了很多逻辑,大体分为3步
加载-链接-创建

加载

加载分为3步
打开冰箱门
通过限定名找类
把大象装进去
将字节码(class里面的各种code内容)转换到方法区
关闭冰箱门
生成一个lang.java.class标记当前加载类信息,提供方法区的入口

链接

链接也分为三部
验证
前面把code码已经放入方法区了,现在需要验证这个加载进来的类的结构是否正确,数量啊是否对得上等等
准备
上面正确了,开始分配内存了
引用
之前类里面各种#号变成真实的地址的引用

创建

创建主要是为了给当前类的变量赋值

以上就是类加载器的工作流程

JVM中都包含哪些类加载器?

4个类加载器
BootstrapClassLoader(根类加载器)
ExtensionClassLoader(扩展类加载器)
SystemClassLoader\APP ClassLoader(程序类加载器)
ClassLoader(自定义类加载器)
都是干啥的?
BootstrapClassLoader 这个主要是负责加载java核心代码的(system啥的)
ExtensionClassLoader 负责加载扩展包的类加载(javax啥的)
SystemClassLoader\APP ClassLoader 负责程序类的加载(自己写的代码)
ClassLoader咋加载的?
执行一个程序前会启动一个jvm实例,这个jvm实例启动时,首先在虚拟机启动的时候会先启动BootstrapClassLoader,这个类加载器会先加载各种各样的核心代码,然后将launcher这个关键类加载并实例,launcher实例以后会依次将 ExtensionClassLoader和SystemClassLoader\APP ClassLoader实例(注意这两个classLoader是单例的,也就证明一个jvm实例中只存在一个ExtensionClassLoader和SystemClassLoader\APP ClassLoader)
其中launcher代码如下

rt.jar包中Launcher代码

常说的双亲委托是什么?
看双亲委托之前,我们先看下ExtensionClassLoader和APP ClassLoader的代码

 static class ExtClassLoader extends URLClassLoader {
        private static volatile Launcher.ExtClassLoader instance;
//单例模式
        public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
            if (instance == null) {
                Class var0 = Launcher.ExtClassLoader.class;
                synchronized(Launcher.ExtClassLoader.class) {
                    if (instance == null) {
                        instance = createExtClassLoader();
                    }
                }
            }

            return instance;
        }
//创建时传入Launcher.ExtClassLoader.getExtDirs
        private static Launcher.ExtClassLoader createExtClassLoader() throws IOException {
            try {
                return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
                    public Launcher.ExtClassLoader run() throws IOException {
                        File[] var1 = Launcher.ExtClassLoader.getExtDirs();
                        int var2 = var1.length;

                        for(int var3 = 0; var3 < var2; ++var3) {
                            MetaIndex.registerDirectory(var1[var3]);
                        }

                        return new Launcher.ExtClassLoader(var1);
                    }
                });
            } catch (PrivilegedActionException var1) {
                throw (IOException)var1.getException();
            }
        }


        public ExtClassLoader(File[] var1) throws IOException {
            super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
            SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
        }
//指定路径为java.ext.dirs
        private static File[] getExtDirs() {
            String var0 = System.getProperty("java.ext.dirs");
            File[] var1;
            if (var0 != null) {
                StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
                int var3 = var2.countTokens();
                var1 = new File[var3];

                for(int var4 = 0; var4 < var3; ++var4) {
                    var1[var4] = new File(var2.nextToken());
                }
            } else {
                var1 = new File[0];
            }

            return var1;
        }

 ...省略部分代码
    }

在URLClassLoader中关键代码如下

public class URLClassLoader extends SecureClassLoader implements Closeable {
   /* The search path for classes and resources */
//这里注释说明了该变量是用的查找class的路径
   private final URLClassPath ucp;
//构造函数第一个参数为路径,第二个参数用来指定父Loader,还有很多构造函数就不一一列举了
   public URLClassLoader(URL[] urls, ClassLoader parent) {
       super(parent);
       // this is to make the stack depth consistent with 1.1
       SecurityManager security = System.getSecurityManager();
       if (security != null) {
           security.checkCreateClassLoader();
       }
       this.acc = AccessController.getContext();
       ucp = new URLClassPath(urls, acc);
   }

   URLClassLoader(URL[] urls, ClassLoader parent,
                  AccessControlContext acc) {
       super(parent);
       // this is to make the stack depth consistent with 1.1
       SecurityManager security = System.getSecurityManager();
       if (security != null) {
           security.checkCreateClassLoader();
       }
       this.acc = acc;
       ucp = new URLClassPath(urls, acc);
   }
寻找class时通过
protected Class<?> findClass(final String name)
       throws ClassNotFoundException
   {
       final Class<?> result;
       try {
           result = AccessController.doPrivileged(
               new PrivilegedExceptionAction<Class<?>>() {
                   public Class<?> run() throws ClassNotFoundException {
                       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;
                       }
                   }
               }, acc);
       } catch (java.security.PrivilegedActionException pae) {
           throw (ClassNotFoundException) pae.getException();
       }
       if (result == null) {
           throw new ClassNotFoundException(name);
       }
       return result;
   }

...省略其他代码

这里可以看出ExtensionClassLoader的class查找路径在java.ext.dirs目录也就是我们常说的扩展包目录,如果你把编译好的class放到这个目录也可以被ExtensionClassLoader加载,AppClassLoader也继承自URLClassLoader url指定到java.class.path,这个路径包含很多地方,程序运行的代码最后也会放到该路径。
URLClassLoader 都是继承自SecureClassLoader,SecureClassLoader继承自ClassLoader,ClassLoader中如果parent没有被当做参数传入,会直接执行getSystemClassLoader()获取Launcher里的loader,这个loader就是launcher初始化的时候创建的AppClassLoader
说了上面那么多,最后开始研究双亲委托,核心代码如下
位于ClassLoader类

   protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            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
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    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;
        }
    }

从上面的代码可以看到,当前类加载器如果要加载一个class的时候,首先判断parent是否为空,一般自定义classLoader的parent都是APP AppClassLoader ,所以不为空,然后执行parent.loadClass,相当于执行AppClassLoader.loadClass,在AppClassLoader的loadClass时也会执行判断parent是否为空,AppClassLoader的parent在Launcher初始化的时候代码已经说明是ExtClassLoader,所以又会执行ExtClassLoader.loadClass,ExtClassLoader执行loadClass时,parent为空,所以会执行findBootstrapClassOrNull。这个时候如果加载的类是核心类的话,findBootstrapClassOrNull会找到,如果不是核心类,返回null就会往下执行ExtClassLoader.findclass,我们上面代码已经确定了ExtClassLoader在扩展库的路径里去找,如果你要找的是扩展库里的类,此时就能拿到,如果不是扩展库里的类,就会AppClassLoader. parent.findClass返回null,往下执行自己的findClass,而大多程序的代码类都在这个时候找到,如果没有找到,就会回到自定义的findClass。
嗯。。。罗里吧嗦一大堆,可能各位没看懂,来个图

image.png

以上图片来源于该文章
简而言之就是,如果存在父加载器,先执行父加载器的loadClass(同时判断父加载器缓存是否存在),不停的这样套娃后,直到没有父加载器,就到boot里找,boot里没找到的话,就一层一层的findClass,直到找到或者抛到最外层为止。
双亲:大多指的是根(BootstrapClassLoader)和父类(parent,包含ExtClassLoader和APPClassLoader)
委托:就是一层一层的往上找。
好处:网上一大堆,主要是加载过得可以通过缓存拿,没加载过的优先父加载器加载,方便以后拿,而且通过url可以分割查找区域
坏处:不常用类也会优先父类加载,占用class缓存。

android中包含哪些类加载器?

android的类加载器大的方向也可以分为三个
1,BootClassLoader(zygote初始化加载preloaded-classes时通过单例初始化,主要用来加载preloaded-classes里的文件,系统常用类)
2,PathClassLoader(zygote fork后,初始化前通过PathClassLoaderFactory.createClassLoader创建的,主要用来加载/data/dalvik-cache-已安装的apk路径,热更新一般都是hook这个classLoader来达到修复bug的)
3,DexClassLoader(可以理解为具有扩展功能的PathClassLoader,主要是可以指定dex的存储路径,默认都是程序内部存储路径,一般插件化都用这个来做)

在android8.0以后,google又增加了一个内存类加载器InMemoryDexClassLoader,这个是一个更特殊的DexClassLoader,主要将本地文件转化为ByteBuffer,相比较DexClassLoader,使用更加开放

类的加载机制也和java一样,至于父子类关系,我给一张debug图,大家自己看


父子关系

以上就是本篇文章内容

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

推荐阅读更多精彩内容