吃透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() {
                    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>() {
                   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图,大家自己看


父子关系

以上就是本篇文章内容

你可能感兴趣的:(吃透JVM篇(3)-jvm的classLoader和android的classLoadder)