[jvm]类的加载流程三

概述

本篇为讲述 android中的 虚拟机
前面是铺垫咯

引入:

人人都知道 android 的代码是用java写的, 以为会做后台开发的就会做android开发了(事实就是这样,只是调用的各种类库,框架不一样而已), 所以 有些面试公司面试时,如果没有android面试官,就会让后台java程序员来做 android 的面试官.
当问到虚拟机这块时,

虚拟机中的顶级类加载器是哪个?
继承关系是什么样的?

后台答案:

1 Bootstrap ClassLoader

2 AppClassLoader/ExtClassLoader-->URLClassLoader-->SecureClassLoader-->Bootstrap ClassLoader

android答案:

1 BootClassLoader

2 PathClassLoader/DexClassLoader-->BaseDexClassLoader-->BootClassLoader

相信一开始面试官是懵逼的,心中一定会想,这怎么和我知道的不一样,这家伙也不像乱说呀. 面试过后,百度一下就会发现,
java后台与android 使用的虚拟机 两者 大相径庭.

java与android 二者使用的虚拟机不同

先看表,有一个大体认识

对比项目javaandroid4.4之前android5.0之后
虚拟机jvmDVMART
可解析文件格式.class.dex/.odex.dex/.odex
classloader的关系体系不同与jvm体系不同与jvm体系不同
classloader种类系统ClassLoader和自定义classloader系统ClassLoader和自定义classloader系统ClassLoader和自定义classloader
运行架构基于栈运行基于寄存器运行基于寄存器运行
编译方式JIT即时编译技术JIT即时编译技术/4.4部分,5.0后所有 AOT 预编译技术
单次指令占用空间1字节2字节(携带的信息比1字节多效率高)2字节(携带的信息比1字节多效率高)
java 与android classloader 对象间的关系

虽然都是系统classloader和自定义classloader两类,但是其中内容和架构都是不同的
正如之前面试场景中的答案那样,在系统classloader中

java的三种为

  1. Bootstrap ClassLoader(java的顶级 加载器)
  2. Extensions ClassLoader
  3. App ClassLoader

android 的为

  1. BootClassLoader(android的顶级加载器)
  2. PathClassLoader
  3. DexClassLoader

二者的顶级加载器所在位置不同
java中的在c层,java层访问不到该对象
android中的在java层,java层可以访问到该对象

android 中的classloader

BootClassLoader

BootClassLoader是ClassLoader的内部类,并继承自ClassLoader。 代码为 andorid 28

所在路径
package java.lang;

static private class SystemClassLoader {
        public static ClassLoader loader = ClassLoader.createSystemClassLoader();
}

	private static ClassLoader createSystemClassLoader() {
        String classPath = System.getProperty("java.class.path", ".");
        String librarySearchPath = System.getProperty("java.library.path", "");
        return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
   	}


class BootClassLoader extends ClassLoader {

    private static BootClassLoader instance;

    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }

//注意看这里, 构造方法中父类传入的是null,所以 bootclassloader中是没有父类的,它自己就是顶级父类
    public BootClassLoader() {
        super(null);
    }
    ......
}

BootClassLoader是一个单例类,需要注意的是BootClassLoader的访问修饰符是默认的,只有在同一个包中才可以访问,因此我们在应用程序中是无法直接调用的。

PathClassLoader

Android系统使用PathClassLoader来加载系统类和应用程序的类,继承自BaseDexClassLoader,实现也都在BaseDexClassLoader中
所在路径package dalvik.system

8.0以上PathClassLoader也可以加载未安装的apk(包含dex的压缩文件)

DexClassLoader

所在路径package dalvik.system

DexClassLoader可以加载dex文件以及包含dex的压缩文件(apk和jar文件),
不管是加载哪种文件,最终都是是加载dex文件,为了方便理解和叙述,将dex文件以及包含dex的压缩文件统称为dex相关文件。
继承自BaseDexClassLoader ,方法实现都在BaseDexClassLoader中。热加载热修复热更新的基础,都是在这个基础上来的

InMemoryDexClassLoader

主要加载内存中的dex文件

andorid 类加载器间的关系

1

在这里插入图片描述

Android中具体负责类加载的并不是哪个ClassLoader,而是通过DexFile的defineClassNative()方法来加载的。

andorid中的类加载机制 也是双亲委派机制
在Android中如果parent类加载器找不到类,最终还是会调用ClassLoader对象自己的findClass()方法。这个与在Java中逻辑是一样的。

ps : 在android studio中 看到的 此部分源码几乎都是throw new RuntimeException("Stub!")

public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }

    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }
}


我要是个小白 还以为实现就是这样的呢(好吧,我就是小白), 其实是因为 此部分代码无法使用android studio查看,
查看源码方式 为
[源码网站一] (http://androidxref.com)
[源码网站二] (https://www.androidos.net.cn/sourcecode)

或者干脆 你可以将源码下载下来 使用api 文档浏览器 Dash (mac 电脑)https://kapeli.com/dash

言归正传 在BaseDexClassLoader

@Override
130    protected Class<?> findClass(String name) throws ClassNotFoundException {
131        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
				此处为在一个集合中找 需要加载的类
132        Class c = pathList.findClass(name, suppressedExceptions);
133        if (c == null) {
134            ClassNotFoundException cnfe = new ClassNotFoundException(
135                    "Didn't find class \"" + name + "\" on path: " + pathList);
136            for (Throwable t : suppressedExceptions) {
137                cnfe.addSuppressed(t);
138            }
139            throw cnfe;
140        }
141        return c;
142    }

DexPathList 源码中 可以看到 在pathList 中找需要加载的类 看看之前是否加载过该类

    public Class<?> findClass(String name, List<Throwable> suppressed) {
485        for (Element element : dexElements) {
					此处是在找这个类是否存在
486            Class<?> clazz = element.findClass(name, definingContext, suppressed);
487            if (clazz != null) {
488                return clazz;
489            }
490        }
491
492        if (dexElementsSuppressedExceptions != null) {
493            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
494        }
495        return null;
496    }

element 是DexPathList的一个内部类


 public Class<?> findClass(String name, ClassLoader definingContext,
723                List<Throwable> suppressed) {
724            return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
725                    : null;
726        }

这个Element数组是DexPathList初始化的时候创建的
这里就有意思了
由于数组是有序的, for循环时,如果找到一个对象,就会返回
那么,如果数组中存在两个同名对象 会如何呢,没错,会先加载 两者中的排前面的一个.有些热加载,热修复,就是这样实现的,在初始化时, 把修复的dex文件放在DexPathList中Element[]数组的前面,这样就实现了修复后的Class抢先加载了,达到了修改bug的目的。

加载class 调用对象到底是谁

Android加载一个Class是调用DexFile的defineClass()方法。而不是调用ClassLoader的defineClass()方法。这一点与Java不同,毕竟Android虚拟机加载的dex文件,而不是class文件。

在Android中ClassLoader的defineClass()方法已经不能用了。可以看到它的方法体里直接抛出异常了,而且在BaseDexClassLoader中也没有重写这个方法,毕竟BaseDexClassLoader加载类的逻辑已经变了。

所以 android中的加载是这样的
ClassLoader.loadClass方法==>BaseDexClassLoader.findClass方法==>DexPathList.findClass方法==>DexFile.loadClassBinaryName方法加载二进制文件

classloader的创建时机

BootClassLoader的创建时机
Zygote进程

PathClassLoader的创建时机
在PathClassLoaderFactory的createClassLoader方法中会创建PathClassLoader

参考

以上源码版本为 android9.0.0_r3
ps: 阅读不同版本的源码 会有所不同,有时,通过对比各个版本的相同功能的实现,可以看到,迭代的实现思路,发现 大神的优化 过程,在自己代码中也可以模仿,不知不觉,自己的 代码风格,和代码格局,也会提高的

这篇帖子 是我整理的知识点,但是里面的知识点,很多前辈都讲过,而我,其实是跟着他们的思路,一点点看源码.有些帖子中的代码,和我看的,是不一样的,就是 源码版本不同的导致的,但这并不影响 理解整体框架. 也感谢各位前辈给指路.

  1. https://blog.csdn.net/zc19921215/article/details/83934489

  2. https://www.jianshu.com/p/c54285a0095b

  3. https://www.jianshu.com/p/7193600024e7

  4. https://blog.csdn.net/mingyunxiaohai/article/details/86677509

  5. https://blog.csdn.net/c10wtiybq1ye3/article/details/86536027


  1. https://www.jianshu.com/p/0820f3aa9eef ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值