【Java】Java的类加载机制

1.类加载的介绍

【Java】Java的类加载机制_第1张图片

 

从上图可以看出,Java文件通过编译变成了.class文件,接下来类加载器又将这些.class文件加载到JVM中,其中类装载器的作用就是类的加载;

类加载概念:它是指将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区内建一个java.lang.Class对象,用来封装类在方法区内的数据结构;类的加载的最终产品是位于堆区内中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内数据结构的方法;

2.加载.class文件的方式

  • 从本地系统中直接加载
  • 通过网络下载.class文件
  • 从zip,jar等归档文件中加载.class文件
  • 从专有数据库中提取.class文件
  • 将Java源文件动态编译为.class文件

3.类的生命周期

包括7个阶段:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)。其中验证、准备、解析三个阶段成为连接(Linking)

【Java】Java的类加载机制_第2张图片

 

加载:查找并加载类的二进制数据

  • 通过一个类的全限定名来获取定义此类的二进制字节流;
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口;

加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式储存在方法区内,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区内的数据。

验证:确保被加载的类的正确性

这一阶段的目的是为了确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害到虚拟机自身的安全;

大致会完成4个阶段的校验动作:

  • 文件格式验证:验证字节流是否符合Class文件格式的规范;
  • 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求;
  • 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的;
  • 符号引用验证:确保解析动作能正确执行;

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

准备阶段是正式为类变量分配内存并设置类变量初始化的阶段,这些变量所使用的内存都将在方法区中进行分配;

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

虚拟机将常量池内的符号引用替换为直接引用的过程;解析动作主要针对类或接口、字段。类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行;

直接引用就是指向目标的指针、相对偏移量或一个简介定位到目标的句柄;

初始化:

为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:声明类变量是指定初始值;使用静态代码块为类变量指定初始值;

JVM初始化步骤:

  • 假如这个类还没有被加载和连接,则程序先加载并连接该类;
  • 假如该类的直接父类还没有被初始化,则先初始化其直接父类;
  • 假如类中有初始化语句,则系统一次执行这些初始化语句;

类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,有以下六种:

  • 创建类的实例(new的方式)。访问某个类或接口的静态变量,或者对该静态变量赋值,调用类的静态方式;
  • 反射的方式;
  • 访问某个类或接口的静态变量,或者对该静态变量赋值;
  • 调用类的静态方法;
  • 初始化某个类的子类,则其父类也会被初始化
  • Java虚拟机启动时被标明为启动类的类,直接使用java.exe命令来运行某个主类(包括main方法的那个类)

结束生命周期:在以下几种情况下,Java虚拟机将结束生命周期

  • 执行了System.out.exit()方法;
  • 程序正常执行结束;
  • 程序在执行过程中遇到了异常或错误而异常终止;
  • 由于操作系统出现错误而导致Java虚拟机进程终止;

4.类加载器

类加载器可以分为:启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器。

几种类加载器的层次关系如下所示:

【Java】Java的类加载机制_第3张图片

 

注意:这里父类加载器并不是通过继承关系来实现的,而是采用组合实现的

启动类加载器(Bootstrap ClassLoader):

这个类存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,那直接使用null指定父类加载器即可

扩展类加载器(Extension ClassLoader):

这个加载器由sun.misc.Launcher $ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

应用程序类加载器(Application ClassLoader):

这个类加载器由sun.misc.Launcher $App-ClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

类加载的三种方式:

  • 命令行启动应用时候由JVM初始化加载;
  • 通过Class.forName()方法动态加载;
  • 通过ClassLoader.loadClass()方法动态加载;

Class.forName()和ClassLoader.loadClass()区别:

Class.forName():将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;

ClassLoader.loadClass():只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。

注:

Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象 。

5.类加载机制——双亲委派机制

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

加载的逻辑为:先检查是否已经被加载过,若没有加载则调用父加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。

类加载器给用户提供最大的帮助为:可以通过动态的路径去进行类的加载操作;

比较两个类相等的前提:必须是由一个类加载器加载的前提下才有意义。否则,即使两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那么这两个类注定不相等。

 

参考文章:https://www.cnblogs.com/ityouknow/p/5603287.html

 

你可能感兴趣的:(Java)