Android_ClassLoader加载机制、双亲委托、类加载共享、隔离等功能

ClassLoader种类:

-BootClassLoader
-PathClassLoader
-DexClassLoader
-BaseDexClassLoader

BootClassLoader: 主要用于加载系统的类,包括java和android系统的类库。 (例如TextView、Context等)
PathClassLoader: 加载安装应用里面的字节码文件。
DexClassLoader:指定目录中的字节码class文件。
BaseDexClassLoader:是PathClassLoader和DexClassLoader的父类

总结:
  1. 通过上面的介绍,得知:一个应用至少会有两个classLoader类,BootClassLoader用于加载系统类
  2. 另一个是PathClassLoader,加载我们APP里面的类,路径是固定的,只能加载/data/app中的apk,所以无法实现动态加载
  3. 当然我们也可以添加DexClassLoader,实现热修复和动态加载
  4. 无论哪个类加载器加载后,都会缓存到内存中去,下次直接在内存中获取

类加载流程

在这里插入图片描述

类加载的特点

-双亲代理模型特点
-类加载共享功能
-类加载的隔离功能


双亲委托模式

下面是java JVM的几个类加载器 / 右边是android 类加载器

名称 加载 加载路径 父加载器 实现
BootStrap / BootClassLoader 虚拟机的核心类库 sun.boot.class.path 系统
Extension 扩展类库 java.ext.dirs、jre/lib/ext BootStrap Java
System / PathClassLoader 应用类库 classpath、java.class.path Extension Java

注:父子加载器并非继承关系,也就是说子加载器不一定是继承了父加载器

然后我们用一张图来梳理父类关系:

在这里插入图片描述
ClassLoader加载流程:
在这里插入图片描述

总结:
  1. 自定义ClassLoader向自己的上层(Application ClassLoader)请求
  2. Application ClassLoader继续向上层(Extension ClassLoader)请求
  3. Extension ClassLoader继续向上层(Bootstrap ClassLoader)请求
  4. Bootstrap ClassLoader是最上层类加载器,所以它尝试在自己的路径中查找要加载类,如果查找到了就加载类,否则向下层(Extension ClassLoader)反馈自己无法加载类。
  5. Extension ClassLoader从自己的路径中寻找要加载类,找到则加载,找不到则继续向下返回。
  6. Application ClassLoader从自己的路径中寻找要加载类,找到则加载,找不到则继续向下返回。
  7. 自定义ClassLoader从自己的路径中寻找要加载类,找到则加载。由于类加载请求是自定义ClassLoader发起的,所以当它自己也找不到要加载的类时会终止加载并抛出
    ClassNotFoundException。

classLoader是通过双亲代理加载特点:
首先会在会缓存中查找(父类有没有加载过,如果有直接返回。 没有的话,会委托先让父加载器去加载,如果父类不能加载,自己在加载。

优点:
  1. 安全,将核心类库与用户类库隔离,用户不能通过加载器替换核心类库,如String类。(假设有一个开发者自己编写了一个名为java.lang.Object的类,想借此欺骗JVM。现在他要使用自定义ClassLoader来加载自己编写的java.lang.Object类。然而幸运的是,双亲委托模型不会让他成功。因为JVM会优先在BootstrapClassLoader的路径下找到java.lang.Object类,并载入它。)
  2. 效率快\避免类库重复加载
缺点:

委托永远是子加载器去请求父加载器,是单向的,即上层的类加载器无法访问下层的类加载器所加载的类:

在这里插入图片描述

如何解决弊端——使用「SPI」

「SPI」 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。
目前有不少框架用它来做服务的扩展发现, 简单来说,它就是一种动态替换发现的机制。

在这里插入图片描述

每一个线程都有自己的ContextClassLoader,默认以SystemClassLoader为ContextClassLoader。通过Thread.currentThread().getContextClassLoader(),可以把一个ClassLoader置于一个线程的实例之中,使该ClassLoader成为一个相对共享的实例,这样即使是启动类加载器中的代码也可以通过这种方式访问应用类加载器中的类了。

SPI的弊端

「SPI」通过循环加载实现类,显而易见,它会把所有的类一同加载,无论有没有用到,这造成了一定的资源浪费.

类加载器是否一定遵循双亲委托模式?

这个答案是否定的。
双亲委托模型只是JDK提供的ClassLoader类的实现方式。
在实际开发中,我们可以通过自定义ClassLoader,并重写父类的loadClass方法,来打破这一机制。


类加载的共享功能: 我们fromwork层级的类(系统类)一旦被我们顶层的BootClassLoader文件加载过,那么久缓存到内存里面,以后任何地方用到都不会重新加载
类加载的隔离功能: 不同的classLoader加载的类就是不同的类, 哪怕是类名和包名都一样也不行

知识补充:

  Log.d(TAG, TextView.class.getClassLoader()+"");
  Log.d(TAG, new TextView(this).getClass().getClassLoader() + "");
  Log.d(TAG, new Test().getClass().getClassLoader() + "");
  Log.d(TAG, Test.class.getClassLoader()+"");

打印结果:

BootClassLoader
BootClassLoader
dalvik.system.PathClassLoader
dalvik.system.PathClassLoader

不同的android不同的版本 对类 资源的加载有不同的方式,适配很复杂

发布了51 篇原创文章 · 获赞 78 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_39079048/article/details/88126628