ClassLoading

1041642665584_.pic_hd_副本

类的生命周期:

classloadinglifecycle

简述:加载是字节码(.class)文件被ClassLoader装载进方法区并在堆中生成一个class对象引用;链接包括:校验二进制流是否符合JVM规范的验证、为各个变量分配内存赋值默认值的准备、将字符串表示的符号引用解析成直接将引用的解析;初始化则是static块、static变量初始化、类构造器执行的过程。

类加载过程(ClassLoading)

这里的类加载不是指类加载阶段,而是指整个类加载过程,即类加载阶段到初始化完成。
  1. 隐式加载

    • 创建类对象

    • 使用类的静态域

    • 创建子类对象

    • 使用子类的静态域

    • 在JVM启动时,BootStrapLoader会加载一些JVM自身运行所需的class

    • 在JVM启动时,ExtClassLoader会加载指定目录下一些特殊的class

    • 在JVM启动时,AppClassLoader会加载classpath路径下的class,以及main函数所在的类的class文件

  1. 显式加载

    • ClassLoader.loadClass(className),只加载和连接、不会进行初始化

    • Class.forName(String name, boolean initialize,ClassLoader loader); 使用loader进行加载和连接,根据参数initialize决定是否初始化。

动态加载

不管使用什么样的类加载器,类,都是在第一次被用到时,动态加载到JVM的。这句话有两层含义:

  1. Java程序在运行时并不一定被完整加载,只有当发现该类还没有加载时,才去本地或远程查找类的.class文件并验证和加载;
  2. 当程序创建了第一个对类的静态成员的引用(如类的静态变量、静态方法、构造方法——构造方法也是静态的)时,才会加载该类。

包括3个步骤:

一、加载(Loading)

简述:加载是将.class文件从各个来源通过ClassLoader装载到方法区内存,并在堆内存中生成该类的class对象引用

时机:类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它

这里有两个重点:

  • 字节码来源。一般的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译
  • 类加载器。一般包括启动类加载器扩展类加载器应用类加载器,以及用户的自定义类加载器。(Android中是系统用PathClassLoader,插件化热修复方案用的DexClassLoader和所有classLoader的父ClassLoader——BaseClassLoader)

二、链接(Linking)

验证字节码、为类变量分配内存并赋default值,解析该类创建所需要直接引用;

Java在加载了类之后,需要进行链接的步骤,链接简单地说,就是将已经加载的java二进制代码组合到JVM运行状态中去。它包括3个步骤:

验证(Verification)

简述:保证二进制字节码符合虚拟机规范:包括类型正确、访问权限、final类是否错误继承等;

​ 验证是保证二进制字节码在结构上的正确性,具体来说,工作包括检测类型正确性,接入属性正确性(public、private),检查final class 没有被继承,检查静态变量的正确性等。

准备(Preparation)

简述:不执行任何代码,仅为类变量(static)分配内存并赋零值:基本类型为0,引用类型为null;

​ 准备阶段主要是创建静态域,分配空间,给这些域设默认值,需要注意的是两点:一个是在准备阶段不会执行任何代码,仅仅是设置默认值,二个是这些默认值是这样分配的,原生类型全部设为0,如:float:0f,int 0, long 0L, boolean:0(布尔类型也是0),其它引用类型为null。

解析(Resolution)

简述:将符号引用解析成直接饮用(即字符串解析成具体地址)
image
直接引用指的是存于方法区-运行时常量池中的方法引用

​ 解析的过程就是对类中的接口、类、方法、变量的符号引用(处于方法区的类信息)进行解析并定位,解析成直接引用(符号引用就是编码,是用字符串表示某个变量、接口的位置;直接引用就是根据符号引用翻译出来的地址),并保证这些类被正确的找到。解析的过程可能导致其它的类被加载。

三、初始化(Initialization)

类与接口的初始化不同,如果一个类被初始化,则其父类或父接口也会被初始化,但如果一个接口初始化,则不会引起其父接口的初始化。

类的初始化时机:

类的初始化即java虚拟机为类的static静态变量赋予初始值(这和准备阶段设置默认初始值为0是不一样的)。只有类的主动使用才会初始化类

1.类的主动使用(6种):

  1. 创建类的实例:用new语句创建实例 Person ps=new Person();

  2. 调用类的静态变量或对静态变量赋值:

  1. 调用类的静态方法

  2. 调用java API中的反射方法:Class.forName(“Person”);

  3. 初始化子类的时候会先初始化父类(但”父类”是接口的时候,不会先初始化它所实现的接口的,只有在程序在使用接口的静态变量时才会使静态接口初始化)

  4. java虚拟机启动时被标明为启动类的类

2.类的被动使用:

  1. final类型的静态变量在编译的时候能计算出值(即编译时常量,在编译的时候将这个值就放入到常量池中了):
    注: final类型的静态变量在编译的时候不能计算出变量的值(即运行时常量)的时候是会被初始化的

    1
    2
    final static int a=2*3;                 //变量a是编译时常量
    final static int a=(int)Math.random(); //变量a不是是编译时常量(即运行时常量)
  2. “父类”是接口的时候,不会先初始化它所实现的接口的,只有在程序在使用接口的静态变量时才会使静态接口初始化

  3. **ClassLoader类的loadClass(“Person”)**方法的时候,只是对类的加载,不是初始化。Class.forName(“Person”);才会初始化

Other

  • 变量赋值:

    “二.1准备阶段”,是jvm赋static变量初值:包括基本类型赋值默认值0、引用类型赋值null

    ”三、初始化阶段“,则根据程序员自己设置的变量赋值。

————final变量 在运行时初始化、static变量在“三、初始化阶段”初始化,至于默认值则都是在“二.1准备阶段”赋予了———

  • 类加载器

在了解Java的机制之前,需要先了解类在JVM(Java虚拟机)中是如何加载的,这对后面理解java其它机制将有重要作用。

每个类编译后产生一个Class对象,存储在.class文件中,JVM使用类加载器(Class Loader)来加载类的字节码文件(.class),类加载器实质上是一条类加载器链,一般的,我们只会用到一个原生的类加载器,它只加载Java API等可信类,通常只是在本地磁盘中加载,这些类一般就够我们使用了。如果我们需要从远程网络或数据库中下载.class字节码文件,那就需要我们来挂载额外的类加载器。

一般来说,类加载器是按照树形的层次结构组织的,每个加载器都有一个父类加载器。另外,每个类加载器都支持代理模式,即可以自己完成Java类的加载工作,也可以代理给其它类加载器。

类加载器的加载顺序有两种,一种是父类优先策略,一种是是自己优先策略,父类优先策略是比较一般的情况(如JDK采用的就是这种方式),在这种策略下,类在加载某个Java类之前,会尝试代理给其父类加载器,只有当父类加载器找不到时,才尝试自己去加载。自己优先的策略与父类优先相反,它会首先尝试子经济加载,找不到的时候才要父类加载器去加载,这种在web容器(如tomcat)中比较常见。

Author

white crow

Posted on

2021-10-26

Updated on

2024-04-19

Licensed under