ClassLoader的具体做用就是将class文件加载到jvm虚拟机中去,程序就能够正确运行了。可是,jvm启动的时候,并不会一次性加载全部的class文件,而是根据须要去动态加载。java
class文件是字节码格式文件。程序员
Dalvik虚拟机如同其余Java虚拟机同样,在运行程序时首先须要将对应的类加载到内存中。而在Java标准的虚拟机中,类加载能够从class文件中读取,也能够是其余形式的二进制流。所以,咱们经常利用这一点,在程序运行时手动加载Class,从而达到代码动态加载执行的目的。缓存
JVM(虚拟机)把描述类的数据的字节码.Class文件加载到内存,并对数据进行校订、转换解析和初始化,最终造成能够被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。安全
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括以下七个阶段,其中验证、准备、解析三个部分统称为连接:数据结构
其中加载、连接(验证、准备、解析)、初始化为类的加载过程。下面咱们来介绍一下类加载的每一步。jvm
一、加载(查找并加载类的二进制数据):函数
加载阶段是“类加载机制”中的一个阶段,这个阶段一般也被称做“装载”,主要完成以下工做:布局
(1)经过“类全名”来获取定义此类的二进制字节流优化
(2)将字节流所表明的静态存储结构转换为方法区的运行时数据结构spa
(3)在java堆中生成一个表明这个类的java.lang.Class对象,做为方法区这些数据的访问入口
相对于类加载过程的其余阶段,加载阶段(准确的说,是加载阶段中获取二进制字节流的动做)是开发期可控性最强的阶段,
由于加载阶段可使用系统提供的类加载器(ClassLoader)来完成,也能够由用户自定义的类加载器完成,开发人员能够经过定义本身的类加载器去控制字节流的获取方式。
加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,方法区中的数据存储格式由虚拟机实现自行定义。
虚拟机并未规定此区域的具体数据结构。而后在java堆中实例化一个java.lang.Class类的对象,这个对象做为程序访问方法区中的这些类型数据的外部接口。以下图:
二、验证(确保被加载类的正确性):
验证是连接阶段的第一步,这一步主要目的是确保class文件的字节流中包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身安全。
验证阶段主要包含以下四个检验过程:
(1)文件格式验证:验证class文件格式规范。
(2)元数据验证:这个阶段是对字节码描述的信息进行语义分析,以保证描述的信息符合java语言规范要求。
(3)字节验证码:进行数据流和控制流分析,这个阶段对类的方法体进行校验分析,这个阶段的任务是保证被校验类的方法在运行时不会作出危害虚拟机安全的行为。
(4)符号引用验证:符号引用中经过字符串描述的全限定名是否能找到对应的类、符号引用类中的类,字段和方法的访问性(private、protected、public、default)是否可被当前类访问。
三、准备(为类的静态变量分配内存,并将其初始化为默认值):
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。注意下面两点:
(1)这时候进行内存分配的仅包括类变量(static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一块儿分配在java堆中。
(2)这里所说的初始值“一般状况”下是数据类型的零值。好比:public static int value = 12;那么变量value在准备阶段事后的初始值为0而不是12,由于这时候还没有开始执行任何java方法,而把value赋值为12的动做将在初始化阶段才会被执行。
四、解析(把类中的符号引用转换为直接引用):
解析阶段是虚拟机常量池内的符号引用替换为直接引用的过程。
符号引用:符号引用是一组符号来描述所引用的目标对象,符号能够是任何形式的字面量,只要使用时能无歧义地定位到目标便可。符号引用与虚拟机实现的内存布局无关,引用的目标对象并不必定已经加载到内存中。
直接引用:直接引用能够是直接指向目标对象的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机内存布局实现相关的,同一个符号引用在不一样的虚拟机实例上翻译出来的直接引用通常不会相同,若是有了直接引用,那引用的目标一定已经在内存中存在。
五、初始化(为类的静态变量赋予正确的初始值):
类的初始化阶段是类加载过程的最后一步,在准备阶段,类变量已赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员经过程序制定的主观计划去初始化类变量和其余资源,或者能够从另一个角度来表达:初始化阶段是执行类构造器<clinit>()方法的过程。在如下四种状况下初始化过程会被触发执行:
(1)遇到new、getstatic、putstatic或invokestatic这四个字节码指令时,若是类没有进行过初始化,则需先触发其初始化。
(2)使用java.lang.reflect包的方法对类进行反射调用的时候。
(3)当初始化一个类的时候,若是发现其父类尚未进行过初始化、则须要先触发其父类的初始化。
(4)jvm启动时,用户指定一个执行的主类(包含main方法的那个类),虚拟机会先初始化这个类。
JVM设计者把类加载阶段中的“经过‘类全名’来获取定义此类的二进制字节流”这个动做放到Java虚拟机外部去实现,以便让应用程序本身决定如何去获取所须要的类。实现这个动做的代码模块称为“类加载器”。
一、类与类加载器
对于任何一个类,对须要由加载它的类加载器和这个类来确立其在JVM中的惟一性。也就是说,两个类来源于同一个Class文件,而且被同一个类加载器加载,这两个类才相等。
二、JVM三种预约义类型类加载器
当一个JVM启动的时候,Java缺省开始使用以下三种类型类加载器:
(1)启动类加载器(Bootstrap ClassLoader):负责加载JAVA_HOME\lib目录中而且能被虚拟机识别的类库到JVM内存中,若是名称不符合的类库即便放在lib目录中也不会被加载。该类加载器没法被Java程序直接引用。
(2)扩展类加载器(Extension ClassLoader):该加载器主要是负责加载JAVA_HOME\lib\,该加载器均可以被开发者直接使用。
(3)应用程序类加载器(Application ClassLoader):该类加载器也成为系统类加载器,它负责加载用户类路径(Classpath)上所指定的类库,开发者能够直接使用该类加载器,若是应用程序中没有自定义过本身的类加载器,通常状况下这个就是程序中默认的类加载器。
对于上面三种类加载器,其中启动类加载器(Bootstrap ClassLoader)使用C++语言实现,属于虚拟机自身的一部分,不是ClassLoader的子类。全部其余的类加载器,这些类加载器是由Java语言实现,独立于JVM外部,而且所有继承自抽象类lava.lang.ClassLoader。
三、双亲委派模型
应用程序都是经过上面的三种类加载器相互配合进行加载的,咱们也能够加入本身定义的类加载器。这些类加载器的关系以及工做过程以下:
如上图所示类加载器之间的这种层次关系,就称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其他的类加载器都应当有本身的父类加载器。子类加载器和父类加载器不是以继承的关系来实现,而是经过组合关系来复用父加载器的代码。
双亲委派模型的工做过程为:若是一个类加载器收到了类加载的请求,它首先不会本身去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每个层次的加载器都是如此,所以全部的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈本身没法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试本身去加载。
Dalvik虚拟机如同其余Java虚拟机同样,在运行程序时首先须要将对应的类加载到内存中。而在Java标准的虚拟机中,类加载能够从class文件中读取,也能够是其余形式的二进制流,所以,咱们经常利用这一点,在程序运行时手动加载Class,从而达到代码动态加载执行的目的。
然而Dalvik虚拟机毕竟不算是标准的Java虚拟机,所以在类加载机制和是哪一个,它们有相同的地方,也有不一样之处,咱们必须区别对待。
在Android中提供了两个ClassLoader的子类:PathClassLoader是经过构造函数new DexFile(path)来产生DexFile对象的;而DexClassLoader则是经过其静态方法loadDex(path,outpath,0)获得DexFile对象。DexClassLoader和PathClassLoader。这二者的区别在于DexClassLoader须要提供一个可写的outpath路径,用来释放.apk包或者.jar包中的dex文件。换个说法来讲,就是PathClassLoader不能主动从zip包中释放出dex,所以只支持直接操做dex格式文件,或者已经安装的apk(由于已经安装的apk在cache中存在缓存的dex文件)。而DexClassLoader能够支持.apk、.jar和.dex文件,而且会在指定的outpath路径释放出dex文件。
参考:https://www.jianshu.com/p/a620e368389a