【0153】【热修复与插件化-1】ClassLoader-JVN 理论
1.热修复和插件化解决的问题





【讲解的模块】


【说明】在接入热修复之后,版本发布具有较大的改变;



2.class文件与dex文件解析
【本章提要】

3. Class文件的解析

3.1 Class文件的认识
【解释】Class文件:能够被JVM识别,加载并执行的文件格式;

【说明】有很多的语言都会生成class文件;

【生成class文件的来源】

【执行class文件】

【实例】手动生成class文件;

【class文件的作用】class文件中包含的信息远远大于java源码中所有包含的信息;
[例如:this/super关键字]this/super关键字的指代信息都是在jvm虚拟机在生成class关键字的时候,记录了本class文件的this/super的指代关系;

3.2 Class文件的结构
【说明】参见文章:https://blog.csdn.net/a19881029/article/details/16117251




【使用010editor可以查看class文件】网上具有破解版:也可以从网盘工具中下载;


4.dex文件解析

4.1 什么是dex文件
【说明】除了java可以生成dex文件,c/c++也可以生成dex文件;

4.2 生成dex文件
【说明】在ide编译生成之后的apk文件解压之后就会出现dex文件;


配置一下 环境变量:路径是dx.bat下的目录

【编译生成dex】
javac -target 1.6 -source 1.6 Hello.java
dx --dex --output Hello.dex Hello.class
adb push Hello.dex /storage/emulated/0
adb shell
dalvikvm -cp /sdcard/Hello.dex Hello

【运行dex文件】

4.4 dex文件的作用
【说明】生成的是整个工程的;与class(所有类)的最大的区别;


4.5 dex的文件结构





Dex文件-----Android平台上的可执行文件 Android虚拟机Dalvik支持的字节码文件格式Google在新发布的Android平台上使用了自己的Dalvik虚拟机 来定义, 这种虚拟机执行的并非Java字节码, 而是另一种字节码: dex格式的字节码。在编译Java代码之后, 通过Android平台上的工具可以将Java字节码转换成Dex字节码。虽然Google称Dalvik是为了移动设备定 做的,但是业界很多人认为这是为了规避向sun申请Javalicense。这个DalvikVM针对手机程式/CPU做过最 佳化,可以同时执行许多VM而不会占用太多Resource。 =================================== Class文件------Java编译后的目标文件 不像J2se,java编译成class就可以直接运行,android平台上class文件不能直接在android上运行。 由于Google 使用了自己的Dalvik来运行应用, 所以这里的class也肯定不能在AndroidDalvik的java环境中运行, android 的class文件实际上只是编译过程中的中间目标文件,需要链接成dex文件后才能在dalvik上运行
5.虚拟机深入讲解
【内容提要】

5.1 Java虚拟机结构解析


【说明】最核心就是Java编译器,如果可以写一个编译器,则可以创造一门语言出来;





5.2 JVM的内存管理

5.3 【栈区】

【栈帧】

栈帧
栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,他是虚拟机运行时数据区的虚拟机栈的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态链接和方法的返回地址等信息。
每一个方法从调用开始直至执行完成的过程,都对应的一个栈帧在虚拟机栈里入栈和出栈的过程。
在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的Code属性中,因此一个栈帧需要多大的内存,不会受到程序运行期变量数据的影响。
一个线程的方法调用链可能会很长,很多方法会同时处于执行状态。对于执行引擎来说,在当前活动的线程中,只有位于栈顶的栈帧才是有效的,成为当前栈帧,与这个栈帧相关联的方法成为当前方法。

【本地栈】

5.4 方法区

5.5 堆区


【摘抄别人的博客的内容】【转载地址】https://www.cnblogs.com/E-star/p/5556188.html
不分代完全可以,分代的唯一理由就是优化GC性能。
你先想想,如果没有分代,那我们所有的对象都在一块,GC的时候我们要找到哪些对象没用,这样就会对堆的所有区域进行扫描。
而我们的很多对象都是朝生夕死的,如果分代的话,我们把新创建的对象放到某一地方,当GC的时候先把这块存“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。
2.年轻代中的GC(JVM garbage collection)
HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1,为啥默认会是这个比例,接下来我们会聊到。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。
对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。
因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。
在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
3.一个对象的这一辈子
我是一个普通的Java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。
4.有关年轻代的JVM参数
1)-XX:NewSize和-XX:MaxNewSize
用于设置年轻代的大小,建议设为整个堆大小的1/3或者1/4,两个值设为一样大。
2)-XX:SurvivorRatio
用于设置Eden和其中一个Survivor的比值,这个值也比较重要。
3)-XX:+PrintTenuringDistribution
这个参数用于显示每次Minor GC时Survivor区中各个年龄段的对象的大小。
4).-XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold
用于设置晋升到老年代的对象年龄的最小值和最大值,每个对象在坚持过一次Minor GC之后,年龄就加1。
========================================================================================
6.JVM的垃圾回收机制
6.1 对象是否“已死”算法
【引用计数算法】

1.对象是否“已死”算法——引用计数器算法
对象中添加一个引用计数器,如果引用计数器为0则表示没有其它地方在引用它。如果有一个地方引用就+1,引用失效时就-1。
看似搞笑且简单的一个算法,实际上在大部分Java虚拟机中并没有采用这种算法,因为它会带来一个致命的问题——对象循环引用。
对象A指向B,对象B反过来指向A,此时它们的引用计数器都不为0,但它们俩实际上已经没有意义因为没有任何地方指向它们。所以又引出了下面的算法。

【可达性算法】

2.对象是否“已死”算法——可达性分析算法
这种算法可以有效地避免对象循环引用的情况,整个对象实例以一个树呈现,根节点是一个称为“GC Roots”的对象,
从这个对象开始向下搜索并作标记,遍历完这棵树过后,未被标记的对象就会判断“已死”,即为可被回收的对象。
【引用】


6.2 垃圾回收算法--标记清除算法
【说明】
【优点】不需要对象的移动,只对不存活的对象进行处理,在存活对象较多的情况下,极为高效;
【缺点】会造成内存的碎片,不利于内存的再次分配;

算法的执行过程与名字一样,先标记所有需要回收的对象,在标记完成后统一回收所有被标记的对象。该算法有两个问题:
- 标记和清除过程效率不高。主要由于垃圾收集器需要从GC Roots根对象中遍历所有可达的对象,并给这些对象加上一个标记,表明此对象在清除的时候被跳过,然后在清除阶段,垃圾收集器会从Java堆中从头到尾进行遍历,如果有对象没有被打上标记,那么这个对象就会被清除。显然遍历的效率是很低的
- 会产生很多不连续的空间碎片,所以可能会导致程序运行过程中需要分配较大的对象的时候,无法找到足够的内存而不得不提前出发一次垃圾回收。
6.3 垃圾回收算法--复制算法
【说明】需要一块内存进行交换;

复制算法是为了解决标记-清除算法的效率问题的,
其思想如下:将可用内存的容量分为大小相等的两块,每次只使用其中的一块,当这一块内存使用完了,就把存活着的对象复制到另外一块上面,然后再把已使用过的内存空间清理掉。
- 优点:每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
- 缺点:算法的代价是将内存缩小为了原来的一半,未免太高了一点。
现在的商业虚拟机都采用这种收集算法来回收新生代,新生代中的对象98%是“朝生夕死”的,所以并不需要按照1∶1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,
每次使用Eden和其中一块Survivor。
当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。
HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1,也就是每次新生代中可用内存空间为整个新生代容量的90%,只有10%的内存会被“浪费”。
当然,90%的对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时(例如,存活的对象需要的空间大于剩余一块Survivor的空间),需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)。
6.4 垃圾回收算法--标记整理算法
【说明】在最后一步的时候,对空闲的内存空间进行了移动,防止了内存的碎片;

复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,
就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
与标记-清除算法过程一样,只不过在标记后不是对未标记的内存区域进行清理,二是让所有的存活对象都向一端移动,然后清理掉边界外的内存。该方法主要用于老年代。
6.5 分代收集算法
目前商用虚拟机都使用“分代收集算法”,所谓分代就是根据对象的生命周期把内存分为几块,一般把Java堆中分为新生代和老年代,这样就可以根据对象的“年龄”选择合适的垃圾回收算法。
- 新生代:“朝生夕死”,存活率低,使用复制算法。
- 老年代:存活率较高,使用“标记-清除”算法或者“标记-整理”算法。
6.6 触发垃圾回收的机制
【说明】即使在手动调用了System.gc()方法,垃圾回收机制也不会立刻调用,还是会等待自己的计算和调用;

7. 虚拟机的不同


ART采取了AOT(Ahead-Of-Time)技术,简单一点理解就是,在APK安装的时候就会做预先编译动作,编译好的文件是OAT文件,
该文件本质上是一个ELF文件,这里与dex(Odex)文件最大的区别是OAT文件不再是字节码文件,而是一个可执行文件,可以更底层的与硬件接触
,运行时也省去了预编译和转译的时间。
AOT,JIT是什么? JIT,即Just-in-time,动态(即时)编译,边运行边编译;AOT,Ahead Of Time,指运行前编译,是两种程序的编译方式 区别 这两种编译方式的主要区别在于是否在“运行时”进行编译 优劣 【JIT优点】: 可以根据当前硬件情况实时编译生成最优机器指令(ps. AOT也可以做到,在用户使用是使用字节码根据机器情况在做一次编译) 可以根据当前程序的运行情况生成最优的机器指令序列 当程序需要支持动态链接时,只能使用JIT 可以根据进程中内存的实际情况调整代码,使内存能够更充分的利用 【JIT缺点】: 编译需要占用运行时资源,会导致进程卡顿 由于编译时间需要占用运行时间,对于某些代码的编译优化不能完全支持,需要在程序流畅和编译时间之间做权衡 在编译准备和识别频繁使用的方法需要占用时间,使得初始编译不能达到最高性能 【AOT优点】: 在程序运行前编译,可以避免在运行时的编译性能消耗和内存消耗 可以在程序运行初期就达到最高性能 可以显著的加快程序的启动 【AOT缺点】: 在程序运行前编译会使程序安装的时间增加 牺牲Java的一致性 将提前编译的内容保存会占用更多的空间
与Android的关联 Android在2.2的时候引入JIT,在kitkat时新增了ART(Android RunTime),在Android L时使用ART完全替代了Dalvik作为默认的虚拟机环境。 Dalvik Dalvik使用JIT 使用.dex字节码,是针对Android设备优化后的DVM所使用的运行时编译字节码 .odex是对dex的优化,deodex在系统第一次开机时会提取所有apk内的dex文件,odex优化将dex提前提取出,加快了开机的速度和程序运行的速度
ART ART 使用AOT 在安装apk时会进行预编译,生成OAT文件,仍以.odex保存,但是与Dalvik下不同,这个文件是可执行文件 dex、odex 均可通过dex2oat生成oat文件,以实现兼容性 在大型应用安装时需要更多时间和空间 Android N引入的混合编译 在Android N中引入了一种新的编译模式,同时使用JIT和AOT。这是我在网上找到的一些解释: 包含了一个混合模式的运行时。应用在安装时不做编译,而是解释字节码,所以可以快速启动。ART中有一种新的、更快的解释器,通过一种新的JIT完成,但是这种JIT的信息不是持久化的。取而代之的是,代码在执行期间被分析,
分析结果保存起来。然后,当设备空转和充电的时候,ART会执行针对“热代码”进行的基于分析的编译,其他代码不做编译。为了得到更优的代码,ART采用了几种技巧包括深度内联。 对同一个应用可以编译数次,或者找到变“热”的代码路径或者对已经编译的代码进行新的优化,这取决于分析器在随后的执行中的分析数据。 这些大概说的是新的ART在安装程序时使用JIT,在JIT编译了一些代码后将这些代码保存到本地,等到设备空闲的时候将保存的这些代码使用AOT编译生成可执行文件保存到本地,待下次运行时直接使用,
并且不断监视代码的更新,在代码有更新后重新生成可执行文件。
8.ClassLoader原理讲解
【 内容提要】

8.1 Android 中的classLoader

Android 中的classLoader分为两种类型,分别是系统ClassLoader和自定义ClassLoader。
其中系统ClassLoader包括三种分别是BootClassLoader、PathClassLoader和DexClassLoader。
1.1 BootClassLoader
【说明】一般加载Framwork层的类;
Android系统启动时会使用BootClassLoader来预加载常用类,与Java中的BootClassLoader不同,它并不是由C/C++代码实现,而是由Java实现的,BootClassLoade的代码如下所示。
libcore/ojluni/src/main/java/java/lang/ClassLoader.java
BootClassLoader是ClassLoader的内部类,并继承自ClassLoader。BootClassLoader是一个单例类,需要注意的是BootClassLoader的访问修饰符是默认的,
只有在同一个包中才可以访问,因此我们在应用程序中是无法直接调用的。
1.2 PathClassLoader
Android系统使用PathClassLoader来加载已经安装到手机中的apk文件中的classLoader;
系统类和应用程序的类,如果是加载非系统应用程序类,则会加载data/app/目录下的dex文件以及包含dex的apk文件或jar文件,不管是加载那种文件,最终都是要加载dex文件,在这里为了方便理解,我们将dex文件以及包含dex的apk文件或jar文件统称为dex相关文件。PathClassLoader不建议开发直接使用。
1.3 DexClassLoader
DexClassLoader可以加载指定目录下的字节码文件;
dex文件以及包含dex的apk文件或jar文件,也支持从SD卡进行加载,这也就意味着DexClassLoader可以在应用未安装的情况下加载dex相关文件。因此,它是热修复和插件化技术的基础。
DexClassLoader构造方法的参数要比PathClassLoader多一个optimizedDirectory参数,参数optimizedDirectory代表什么呢?我们知道应用程序第一次被加载的时候,为了提高以后的启动速度和执行效率,Android系统会对dex相关文件做一定程度的优化,并生成一个ODEX文件,此后再运行这个应用程序的时候,只要加载优化过的ODEX文件就行了,省去了每次都要优化的时间,而参数optimizedDirectory就是代表存储ODEX文件的路径,这个路径必须是一个内部存储路径。
PathClassLoader没有参数optimizedDirectory,这是因为PathClassLoader已经默认了参数optimizedDirectory的路径为:/data/dalvik-cache。DexClassLoader 也继承自BaseDexClassLoader ,方法实现也都在BaseDexClassLoader中。
1.4.ClassLoader的继承关系
运行一个Android程序需要用到几种类型的类加载器呢?如下所示。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ClassLoader loader = MainActivity.class.getClassLoader();
while (loader != null) {
Log.d("liuwangshu",loader.toString());//1
loader = loader.getParent();
}
}
}
首先我们得到MainActivity的类加载器,并在注释1处通过Log打印出来,接着打印出当前类的类加载器的父加载器,直到没有父加载器终止循环。打印结果如下所示。
10-07 07:23:02.835 8272-8272/? D/liuwangshu: dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/com.example.liuwangshu.moonclassloader-2/base.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_dependencies_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_0_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_1_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_2_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_3_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_4_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_5_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_6_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_7_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_8_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_9_apk.apk”],nativeLibraryDirectories=[/data/app/com.example.liuwangshu.moonclassloader-2/lib/x86, /vendor/lib, /system/lib]]]
10-07 07:23:02.835 8272-8272/? D/liuwangshu: java.lang.BootClassLoader@e175998
可以看到有两种类加载器,一种是PathClassLoader,另一种则是BootClassLoader。DexPathList中包含了很多apk的路径,其中/data/app/com.example.liuwangshu.moonclassloader-2/base.apk就是示例应用安装在手机上的位置。关于DexPathList后续文章会进行介绍。
和Java中的ClassLoader一样,虽然系统所提供的类加载器有3种类型,但是系统提供的ClassLoader相关类却不只3个。ClassLoader的继承关系如下图所示。 
可以看到上面一共有8个ClassLoader相关类,其中有一些和Java中的ClassLoader相关类十分类似,下面简单对它们进行介绍:
- ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能。BootClassLoader是它的内部类。
- SecureClassLoader类和JDK8中的SecureClassLoader类的代码是一样的,它继承了抽象类ClassLoader。SecureClassLoader并不是ClassLoader的实现类,而是拓展了ClassLoader类加入了权限方面的功能,加强了ClassLoader的安全性。
- URLClassLoader类和JDK8中的URLClassLoader类的代码是一样的,它继承自SecureClassLoader,用来通过URl路径从jar文件和文件夹中加载类和资源。
- InMemoryDexClassLoader是Android8.0新增的类加载器,继承自BaseDexClassLoader,用于加载内存中的dex文件。
- BaseDexClassLoader继承自ClassLoader,是抽象类ClassLoader的具体实现类,PathClassLoader和DexClassLoader都继承它。
8.2 正常运行一个app需要的加载类
BootClassLoader和PathClassLoader


05-16 15:49:24.449 15451-15451/www.wsxingjun.com.asynctaskdemo D/wsxingjun: Loader:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/www.wsxingjun.com.asynctaskdemo-1/base.apk", zip file "/data/app/www.wsxingjun.com.asynctaskdemo-1/split_lib_dependencies_apk.apk", zip file "/data/app/www.wsxingjun.com.asynctaskdemo-1/split_lib_slice_0_apk.apk", zip file "/data/app/www.wsxingjun.com.asynctaskdemo-1/split_lib_slice_1_apk.apk", zip file "/data/app/www.wsxingjun.com.asynctaskdemo-1/split_lib_slice_2_apk.apk", zip file "/data/app/www.wsxingjun.com.asynctaskdemo-1/split_lib_slice_3_apk.apk", zip file "/data/app/www.wsxingjun.com.asynctaskdemo-1/split_lib_slice_4_apk.apk", zip file "/data/app/www.wsxingjun.com.asynctaskdemo-1/split_lib_slice_5_apk.apk", zip file "/data/app/www.wsxingjun.com.asynctaskdemo-1/split_lib_slice_6_apk.apk", zip file "/data/app/www.wsxingjun.com.asynctaskdemo-1/split_lib_slice_7_apk.apk", zip file "/data/app/www.wsxingjun.com.asynctaskdemo-1/split_lib_slice_8_apk.apk", zip file "/data/app/www.wsxingjun.com.asynctaskdemo-1/split_lib_slice_9_apk.apk"],nativeLibraryDirectories=[/data/app/www.wsxingjun.com.asynctaskdemo-1/lib/arm, /vendor/lib, /system/lib]]] 05-16 15:49:24.449 15451-15451/www.wsxingjun.com.asynctaskdemo D/wsxingjun: Loader:java.lang.BootClassLoader@2c7f6a1 05-16 15:49:24.479 15451-15546/www.wsxingjun.com.asynctaskdemo D/OpenGLRenderer: Use EGL_SWAP_BEHAVIOR_PRESERVED: true
8.3 ClassLoader的特点及作用
【问】怎样两个类是同一个类呢?
【答】类名+包名是一致的,更重要的是加载类ClassLoader是一致的;

ClassLoader的双亲委托模式:classloader 按级别分为三个级别:最上级 : bootstrap classLoader(根类加载器) ; 中间级:extension classLoader (扩展类加载器) 最低级 app classLoader(应用类加载器)。
根(Bootstrap)类加载器:该加载器没有父加载器。它负责加载虚拟机的核心类库,如java.lang.*等。例如java.lang.Object就是由根类加载器加载的。根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。根类加载器的实现依赖于底层操作系统,属于虚拟机的实现的一部分,它并没有继承java.lang.ClassLoader类。
扩展(Extension)类加载器:它的父加载器为根类加载器。它从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库,如果把用户创建的JAR文件放在这个目录下,也会自动由扩展类加载器加载。扩展类加载器是纯Java类,是java.lang.ClassLoader类的子类。
系统(System)类加载器:也称为应用类加载器,它的父加载器为扩展类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中加载类,它是用户自定义的类加载器的默认父加载器。系统类加载器是纯Java类,是java.lang.ClassLoader类的子类。
父子加载器并非继承关系,也就是说子加载器不一定是继承了父加载器。
对于Java来说,java 虚拟机要将被用到的java类文件通过classLoader 加载到JVM内存中,而这个classloader就是bootstrap classloader。而对于APP而言,首先请求app 级来加载,继而请求extension classLoader,最后启动bootstrap classLoader 。
自定义ClassLoader
双亲委托模式
通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,
如果父类加载器可以完成类加载任务,就成功返回;
只有父类加载器无法完成此加载任务时,才自己去加载。
为了更好的理解双亲委托模式,我们先自定义一个ClassLoader,假设我们使用这个自定义的ClassLoader加载 java.lang.String,那么这里String是否会被这个ClassLoader加载呢?
事实上java.lang.String这个类并不会被我们自定义的classloader加载,而是由bootstrap classloader进行加载,为什么会这样?
实际上这就是双亲委托模式的原因,因为在任何一个自定义ClassLoader加载一个类之前,它都会先 委托它的父亲ClassLoader进行加载,
只有当父亲ClassLoader无法加载成功后,才会由自己加载。而在上面的例子中,因为 java.lang.String是属于java核心API的一个类,
所以当使用自定义的classloader加载它的时候,该 ClassLoader会先委托它的父亲ClassLoader进行加载(bootstrap classloader),所以并不会被我们自定义的ClassLoader加载。
使用双亲委托模式优点
那么我们使用双亲委托模式有什么好处呢?
- 因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
- 考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader。
【双亲代理模式的源码】


【子类源码】DexClassLoader源码,是动态加载非常重要的一个类;
1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dalvik.system; 18 19 import java.io.File; 20 21 /** 22 * A class loader that loads classes from {@code .jar} and {@code .apk} files //主要是加载jar、apk下的.dex文件; 23 * containing a {@code classes.dex} entry. This can be used to execute code not 24 * installed as part of an application. 25 * 26 * <p>This class loader requires an application-private, writable directory to 27 * cache optimized classes. Use {@code Context.getCodeCacheDir()} to create 28 * such a directory: <pre> {@code 29 * File dexOutputDir = context.getCodeCacheDir(); 30 * }</pre> 31 * 32 * <p><strong>Do not cache optimized classes on external storage.</strong> 33 * External storage does not provide access controls necessary to protect your 34 * application from code injection attacks. 35 */ 36 public class DexClassLoader extends BaseDexClassLoader { 37 /** 38 * Creates a {@code DexClassLoader} that finds interpreted and native 39 * code. Interpreted classes are found in a set of DEX files contained 40 * in Jar or APK files. 41 * 42 * <p>The path lists are separated using the character specified by the 43 * {@code path.separator} system property, which defaults to {@code :}. 44 * 45 * @param dexPath the list of jar/apk files containing classes and 46 * resources, delimited by {@code File.pathSeparator}, which 47 * defaults to {@code ":"} on Android 48 * @param optimizedDirectory directory where optimized dex files 49 * should be written; must not be {@code null} 50 * @param libraryPath the list of directories containing native 51 * libraries, delimited by {@code File.pathSeparator}; may be 52 * {@code null} 53 * @param parent the parent class loader 54 */ 55 public DexClassLoader(String dexPath, String optimizedDirectory, 56 String libraryPath, ClassLoader parent) { 57 super(dexPath, new File(optimizedDirectory), libraryPath, parent); 58 } 59 }
【源码】PathClassLoader 源码,参数与DexClassLoader缺少了【String optimizedDirectory参数】,只能加载安装到手机中的dex文件;
1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dalvik.system; 18 19 /** 20 * Provides a simple {@link ClassLoader} implementation that operates on a list 21 * of files and directories in the local file system, but does not attempt to 22 * load classes from the network. Android uses this class for its system class 23 * loader and for its application class loader(s). 24 */ 25 public class PathClassLoader extends BaseDexClassLoader { 26 /** 27 * Creates a {@code PathClassLoader} that operates on a given list of files 28 * and directories. This method is equivalent to calling 29 * {@link #PathClassLoader(String, String, ClassLoader)} with a 30 * {@code null} value for the second argument (see description there). 31 * 32 * @param dexPath the list of jar/apk files containing classes and 33 * resources, delimited by {@code File.pathSeparator}, which 34 * defaults to {@code ":"} on Android 35 * @param parent the parent class loader 36 */ 37 public PathClassLoader(String dexPath, ClassLoader parent) { 38 super(dexPath, null, null, parent); 39 } 40 41 /** 42 * Creates a {@code PathClassLoader} that operates on two given 43 * lists of files and directories. The entries of the first list 44 * should be one of the following: 45 * 46 * <ul> 47 * <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as 48 * well as arbitrary resources. 49 * <li>Raw ".dex" files (not inside a zip file). 50 * </ul> 51 * 52 * The entries of the second list should be directories containing 53 * native library files. 54 * 55 * @param dexPath the list of jar/apk files containing classes and 56 * resources, delimited by {@code File.pathSeparator}, which 57 * defaults to {@code ":"} on Android 58 * @param libraryPath the list of directories containing native 59 * libraries, delimited by {@code File.pathSeparator}; may be 60 * {@code null} 61 * @param parent the parent class loader 62 */ 63 public PathClassLoader(String dexPath, String libraryPath, 64 ClassLoader parent) { 65 super(dexPath, null, libraryPath, parent); 66 } 67 }
【源码】BaseDexClassLoader.java
1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dalvik.system; 18 19 import java.io.File; 20 import java.net.URL; 21 import java.util.ArrayList; 22 import java.util.Enumeration; 23 import java.util.List; 24 25 /** 26 * Base class for common functionality between various dex-based 27 * {@link ClassLoader} implementations. 28 */ 29 public class BaseDexClassLoader extends ClassLoader { 30 private final DexPathList pathList; //首先定义了此成员变量 31 32 /** 33 * Constructs an instance. 34 * 35 * @param dexPath the list of jar/apk files containing classes and 36 * resources, delimited by {@code File.pathSeparator}, which 37 * defaults to {@code ":"} on Android 38 * @param optimizedDirectory directory where optimized dex files 39 * should be written; may be {@code null} 40 * @param libraryPath the list of directories containing native 41 * libraries, delimited by {@code File.pathSeparator}; may be 42 * {@code null} 43 * @param parent the parent class loader 44 */ 45 public BaseDexClassLoader(String dexPath, File optimizedDirectory, 46 String libraryPath, ClassLoader parent) { 47 super(parent); 48 this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); //【3】在构造方法中增加初始化了pathList变量; 49 } 50 51 @Override 52 protected Class<?> findClass(String name) throws ClassNotFoundException { //【1】核心方法,子类同样继承与此方法 53 List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); 54 Class c = pathList.findClass(name, suppressedExceptions); //【2】通过pathlist中的findClass()查找 55 if (c == null) { 56 ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList); 57 for (Throwable t : suppressedExceptions) { 58 cnfe.addSuppressed(t); 59 } 60 throw cnfe; 61 } 62 return c; 63 } 64 65 @Override 66 protected URL findResource(String name) { 67 return pathList.findResource(name); 68 } 69 70 @Override 71 protected Enumeration<URL> findResources(String name) { 72 return pathList.findResources(name); 73 } 74 75 @Override 76 public String findLibrary(String name) { 77 return pathList.findLibrary(name); 78 } 79 80 /** 81 * Returns package information for the given package. 82 * Unfortunately, instances of this class don't really have this 83 * information, and as a non-secure {@code ClassLoader}, it isn't 84 * even required to, according to the spec. Yet, we want to 85 * provide it, in order to make all those hopeful callers of 86 * {@code myClass.getPackage().getName()} happy. Thus we construct 87 * a {@code Package} object the first time it is being requested 88 * and fill most of the fields with dummy values. The {@code 89 * Package} object is then put into the {@code ClassLoader}'s 90 * package cache, so we see the same one next time. We don't 91 * create {@code Package} objects for {@code null} arguments or 92 * for the default package. 93 * 94 * <p>There is a limited chance that we end up with multiple 95 * {@code Package} objects representing the same package: It can 96 * happen when when a package is scattered across different JAR 97 * files which were loaded by different {@code ClassLoader} 98 * instances. This is rather unlikely, and given that this whole 99 * thing is more or less a workaround, probably not worth the 100 * effort to address. 101 * 102 * @param name the name of the class 103 * @return the package information for the class, or {@code null} 104 * if there is no package information available for it 105 */ 106 @Override 107 protected synchronized Package getPackage(String name) { 108 if (name != null && !name.isEmpty()) { 109 Package pack = super.getPackage(name); 110 111 if (pack == null) { 112 pack = definePackage(name, "Unknown", "0.0", "Unknown", 113 "Unknown", "0.0", "Unknown", null); 114 } 115 116 return pack; 117 } 118 119 return null; 120 } 121 122 /** 123 * @hide 124 */ 125 public String getLdLibraryPath() { 126 StringBuilder result = new StringBuilder(); 127 for (File directory : pathList.getNativeLibraryDirectories()) { 128 if (result.length() > 0) { 129 result.append(':'); 130 } 131 result.append(directory); 132 } 133 return result.toString(); 134 } 135 136 @Override public String toString() { 137 return getClass().getName() + "[" + pathList + "]"; 138 } 139 }
【查找dexFile文件】--先查找dexFile文件,然后将查找到的dexFile文件放到数组中;



【核心】加载文件并new DexFile();

【findClass】循环查找:然后调用DexFile类中的LoadClassBinaryName();




【最终调用的是c中的native方法进行加载的】

【创建的dexFile的数组的流程】

9.动态加载的难点
【说明】在Activity等组件、资源图片等等在Android中需要注册才能使用;

Android 中每个版本对资源和组件的加载的方式也不同;
【总结】动态加载归结为的问题就是需要一个运行上下文的环境;


浙公网安备 33010602011771号