从Java到android:类的加载机制

一直想写一个关于从java到android的系列博客,知道android的用法,更知道为啥会这样。17年开始,一起gogogo。

一、Java的类加载过程

JVM(虚拟机)把描述类的数据的字节码.Class文件加载到内存,并对数据进行校正、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括如下七个阶段,其中验证、准备、解析三个部分统称为链接:

从Java到android:类的加载机制_第1张图片

其中加载、链接(验证、准备、解析)、初始化为类的加载过程。下面我们来介绍一下类加载的每一步。

1、加载(查找并加载类的二进制数据):

加载阶段是“类加载机制”中的一个阶段,这个阶段通常也被称作“装载”,主要完成如下工作:
(1)通过“类全名”来获取定义此类的二进制字节流
(2)将字节流所代表的静态存储结构转换为方法区的运行时数据结构
(3)在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口

相对于类加载过程的其他阶段,加载阶段(准确的说,是加载阶段中获取二进制字节流的动作)是开发期可控性最强的阶段,因为加载阶段可以使用系统提供的类加载器(ClassLoader)来完成,也可以由用户自定义的类加载器完成,开发人员可以通过定义自己的类加载器去控制字节流的获取方式。

加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,方法区中的数据存储格式由虚拟机实现自行定义。虚拟机并未规定此区域的具体数据结构。然后在java堆中实例化一个java.lang.Class类的对象,这个对象作为程序访问方法区中的这些类型数据的外部接口。如下图:

从Java到android:类的加载机制_第2张图片

从Java到android:类的加载机制_第3张图片


2、验证(确保被加载类的正确性):

验证是链接阶段的第一步,这一步主要目的是确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。

验证阶段主要包含如下四个检验过程:
(1)文件格式验证:验证class文件格式规范。
(2)元数据验证:这个阶段是对字节码描述的信息进行语义分析,以保证描述的信息符合java语言规范要求。
(3)字节验证码:进行数据流和控制流分析,这个阶段对类的方法体进行校验分析,这个阶段的任务是保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。
(4)符号引用验证:符号引用中通过字符串描述的全限定名是否能找到对应的类、符号引用类中的类,字段和方法的访问性(private、protected、public、default)是否可被当前类访问。

3、准备(为类的静态变量分配内存,并将其初始化为默认值):

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。注意下面两点:
(1)这时候进行内存分配的仅包括类变量(static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中。
(2)这里所说的初始值“通常情况”下是数据类型的零值。比如:public static int value = 12;那么变量value在准备阶段过后的初始值为0而不是12,因为这时候尚未开始执行任何java方法,而把value赋值为12的动作将在初始化阶段才会被执行。

4、解析(把类中的符号引用转换为直接引用):

解析阶段是虚拟机常量池内的符号引用替换为直接引用的过程。

符号引用:符号引用是一组符号来描述所引用的目标对象,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标对象并不一定已经加载到内存中。

直接引用:直接引用可以是直接指向目标对象的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机内存布局实现相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同,如果有了直接引用,那引用的目标必定已经在内存中存在。

5、初始化(为类的静态变量赋予正确的初始值):

类的初始化阶段是类加载过程的最后一步,在准备阶段,类变量已赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器()方法的过程。在以下四种情况下初始化过程会被触发执行:
(1)遇到new、getstatic、putstatic或invokestatic这四个字节码指令时,如果类没有进行过初始化,则需先触发其初始化。
(2)使用java.lang.reflect包的方法对类进行反射调用的时候。
(3)当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先触发其父类的初始化。
(4)jvm启动时,用户指定一个执行的主类(包含main方法的那个类),虚拟机会先初始化这个类。

二、Java的类加载器

JVM设计者把类加载阶段中的“通过‘类全名’来获取定义此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。

1、类与类加载器

对于任何一个类,对需要由加载它的类加载器和这个类来确立其在JVM中的唯一性。也就是说,两个类来源于同一个Class文件,并且被同一个类加载器加载,这两个类才相等。

2、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。

3、双亲委派模型

应用程序都是通过上面的三种类加载器相互配合进行加载的,我们也可以加入自己定义的类加载器。这些类加载器的关系以及工作过程如下:

从Java到android:类的加载机制_第4张图片

如上图所示类加载器之间的这种层次关系,就称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。子类加载器和父类加载器不是以继承的关系来实现,而是通过组合关系来复用父加载器的代码。

双亲委派模型的工作过程为:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载。

三、Android中的类加载器

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文件。

你可能感兴趣的:(Java,基础)