【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”区被填满之后,会将所有对象移动到年老代中。

young_gc

 

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 垃圾回收算法--标记清除算法

【说明】

【优点】不需要对象的移动,只对不存活的对象进行处理,在存活对象较多的情况下,极为高效;

【缺点】会造成内存的碎片,不利于内存的再次分配;

 

算法的执行过程与名字一样,先标记所有需要回收的对象,在标记完成后统一回收所有被标记的对象。该算法有两个问题:

  1. 标记和清除过程效率不高。主要由于垃圾收集器需要从GC Roots根对象中遍历所有可达的对象,并给这些对象加上一个标记,表明此对象在清除的时候被跳过,然后在清除阶段,垃圾收集器会从Java堆中从头到尾进行遍历,如果有对象没有被打上标记,那么这个对象就会被清除。显然遍历的效率是很低的
  2. 会产生很多不连续的空间碎片,所以可能会导致程序运行过程中需要分配较大的对象的时候,无法找到足够的内存而不得不提前出发一次垃圾回收。

标记清除法

 

6.3 垃圾回收算法--复制算法

【说明】需要一块内存进行交换;

复制算法是为了解决标记-清除算法的效率问题的,

其思想如下:将可用内存的容量分为大小相等的两块,每次只使用其中的一块,当这一块内存使用完了,就把存活着的对象复制到另外一块上面,然后再把已使用过的内存空间清理掉。

  1. 优点:每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
  2. 缺点:算法的代价是将内存缩小为了原来的一半,未免太高了一点。

复制算法

现在的商业虚拟机都采用这种收集算法来回收新生代,新生代中的对象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堆中分为新生代和老年代,这样就可以根据对象的“年龄”选择合适的垃圾回收算法。

  1. 新生代:“朝生夕死”,存活率低,使用复制算法。
  2. 老年代:存活率较高,使用“标记-清除”算法或者“标记-整理”算法。

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的继承关系如下图所示。 
Android ClassLoader继承关系(6).png
可以看到上面一共有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加载。

使用双亲委托模式优点

那么我们使用双亲委托模式有什么好处呢?

  1. 因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
  2. 考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的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 中每个版本对资源和组件的加载的方式也不同;

【总结】动态加载归结为的问题就是需要一个运行上下文的环境;

 

posted @ 2018-05-16 09:02  OzTaking  阅读(650)  评论(0)    收藏  举报