ClassLoader
Introduction
- Java是半编译半解释语言,任何一个.java文件(其实就是一个类文件)都要被jdk的编译器编译成class文件(Android上会把所有的class文件进行一个合并,并对class文件做一些优化,比如去除运行时常亮池里重复的部分),class文件就是jvm的可执行文件,任何一个class文件都必须要被JVM加载(就是一次IO,把class文件读入内存,然后JVM再去进行相应的处理)后才能在运行时使用,一个class文件的加载还分几个步骤:
- 装载
- 等于一次IO
- 校验
- 任何一个文件包括可执行文件、class文件都是可以人为修改或者编写的(只是可执行文件和class文件等我们都是用编译器来自动生成的),所以就可能有一些不合法的指令
- 准备
- 为静态变量分配空间(加载的是类文件,又没有对象,所以当然只能是静态变量了),只是分配,没有赋值动作
- 解析
- 官方说法:把符号引用解析为直接引用,简单且非正式的解释一下,在class文件里,java类的每一个方法和变量都是依靠字符串的形式存储的,而不管怎么样,我们最后肯定是要像C/C++一样拿到方法或变量的地址才能进行调用,这个解析就是做了一部分这样的工作(说一部分的原因是因为多态的特性可能会导致在加载时不能确定具体的方法地址)
- 初始化
- 对静态变量进行赋值,执行静态方法块里的操作,这里的本质其实也是javac在编译时就收集好所有的静态操作,生成一个自动执行的静态方法
- 装载
- ClassLoader就是用来将class文件们正确加载到jvm的类,其实也就是实现了装载这个功能
- 关于类加载和java类的本质只是非正式不严谨的讲述了一下,因为这篇文章如果再讲这些东西就太长了
Parent Delegate Model
其实这个模型就是一个委派(Delegate)模式:
- 当VM需要去加载类文件(字节码)时,会使用ClassLoader去把字节码读取到内存中,而每一个ClassLoader在需要load时,都会先尝试使用自己的父类Loader去加载,如果父类已经加载过了这个Class或者执行了加载操作,那子类就不必再去执行加载操作
- 这样的好处是可以避免重复加载
- 在JVM中,ClassLoader类的委托层次
- Bootstrap ClassLoader: 最顶层的ClassLoader,负责加载JAVA_HOME/lib/下面的标准类库,即如果不是Java的标准类,那Bootstrap会忽略
- Extension ClassLoader: 负责加载JAVA_HOME/lib/ext/下面所有的类
- Application ClassLoader: 负责加载上述两种自带ClassLoader所不能加载的其他的CLASSPATH下的类,也就是说一般我们自己定义的类都是这个ClassLoader加载的
- 注意这些类没有继承层次,都是通过delegate去进行调用的(也就是组合)
- 在Android的Dalvik/ART中:
- BootClassLoader:最顶层的ClassLoader,它相当于JVM中的BootstrapClassLoader,用来加载android的标准类库
- BaseDexClassLoader:ClassLoader的子类,它是PathClassLoader和DexClassLoader的基类,它不是一个abstract类,它的constructor提供了几个参数
- dexPath: 加载的apk或者jar的路径
- optimizedDirectory:从apk或者jar/aar提取出来的odex文件存放的路径,null的话就会放在默认路径下
- libraryPath:C/C++库的目录
- parent:委托对象
- PathClassLoader:optimizedDirectory设置为null,即提取出来odex存放在默认路径,系统加载我们自己定义的类就是用的这个ClassLoader,相当于ApplicationClassLoader,另外在art上貌似加载未安装的apk是可以的,但是在dalvik上是不可以的,这个classloader也不可以从网络上加载
- DexClassLoader:optimizedDirectory是保留的,而且无论是在dalvik上还是art上都可以从任何地方加载odex文件,可以使用它加载我们指定路径上的odex文件,包括从网络上加载
How
defineClass是用来虚拟机用来将读入的字节流转化为Class对象的方法,即从磁盘上读出一个class/dex文件后,怎么样把它转化为对应的Class对象存储在对应的永生代/元空间内,这个方法是Java为我们写好的;在 Android上我们要下载源码才能看到这个方法,否则只是一个throw Exception
loadClass:这个方法定义了类加载的方式,Parent Delegate Model就是在loadClass里面制定的策略,它会先确认自己是否加载过这个类,然后再委托给parent,最后如果仍未加载,自己尝试加载(findClass);如果想要自己定义类加载策略,可以Override这个方法
findClass:定义了如何去寻找这个方法参数里指定的class,一般来说我们只需要重写这个方法
-
定义好了如何使用:
ClassLoader loader = new MyClassLoader(...); loader.loadClass("TestClass");
Why?
- 我们可以动态的指定类加载的地址,比如我们通过网络获取了一个class/dex文件,在app里面把它存储到设置好的地址,然后就可以让应用程序加载使用这个类,这样可以为应用程序新增/改变行为(插件化开发和热修复)
- 可以加载不在ClassPath下的class文件