ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Java虚拟机JVM之类加载机制与类加载器

2019-06-28 09:19:53  阅读:147  来源: 互联网

标签:初始化 Java 虚拟机 static 引用 Class 加载


一、类的生命周期

类的生命周期
加载 --> 验证 --> 准备 --> 解析 --> 初始化 --> 使用 --> 卸载
       |<------- 连接 ------->|
|<------------- 类加载 ---------------->|

类的生命周期一共有 7 个阶段,其中前五个阶段较为重要,统称为类加载,第 2 ~ 4 阶段统称为连接,加载和连接中的三个过程开始的顺序是固定的,但是执行过程中是可以交叉执行的。

二、类加载的时机

JVM会在第一次主动引用类的时候,加载该类,被动引用时并不会引发类加载的操作。也就是说,JVM并不是在一开始就把一个程序中所有的类都加载到内存中,而是在第一次用到的时候才进行加载。

那么什么是主动引用,什么是被动引用呢?

  • 主动引用
    • 遇到 new、getstatic、putstatic、invokestatic 字节码指令,例如:
      • 使用 new 实例化对象;
      • 读取或设置一个类的 static 字段(被 final 修饰的除外);
      • 调用类的静态方法。
    • 对类进行反射调用;
    • 初始化一个类时,其父类还没初始化(需先初始化父类);
      • 这点类与接口具有不同的表现,接口初始化时,不要求其父接口完成初始化,只有真正使用父接口时才初始化,如引用父接口中定义的常量。
    • 虚拟机启动,先初始化包含 main() 函数的主类;
    • JDK 1.7 动态语言支持:一个 java.lang.invoke.MethodHandle 的解析结果为 REF_getStatic、REF_putStatic、REF_invokeStatic。
  • 被动引用
    • 通过子类引用父类静态字段,不会导致子类初始化;
    • Array[] arr = new Array[10]; 不会触发 Array 类初始化;
    • static final VAR 在编译阶段会存入调用类的常量池,通过 ClassName.VAR 引用不会触发 ClassName 初始化。

也就是说,只有发生主动引用所列出的 5 种情况,一个类才会被加载到内存中,也就是说类的加载是 lazy-load 的,不到必要时刻是不会提前加载的,毕竟如果将程序运行中永远用不到的类加载进内存,会占用方法区中的内存,浪费系统资源。

 

三、类的显式加载和隐式加载

1、显示加载

  • 调用 ClassLoader#loadClass(className) 或 Class.forName(className)
  • 两种显示加载 .class 文件的区别:
    • Class.forName(className) 加载 class 的同时会初始化静态域,ClassLoader#loadClass(className) 不会初始化静态域;
    • Class.forName 借助当前调用者的 class 的 ClassLoader 完成 class 的加载。

2、隐式加载

  • new 类对象;
  • 使用类的静态域;
  • 创建子类对象;
  • 使用子类的静态域;
  • 其他的隐式加载,在 JVM 启动时:
    • BootStrapLoader 会加载一些 JVM 自身运行所需的 Class;
    • ExtClassLoader 会加载指定目录下一些特殊的 Class;
    • AppClassLoader 会加载 classpath 路径下的 Class,以及 main 函数所在的类的 Class 文件。

四、类加载的过程

1、加载

加载的过程:加载”是“类加载”过程的一个阶段,不能混淆这两个名词。在加载阶段,虚拟机需要完成 3 件事:

  • 通过类的全限定名获取二进制字节流(将 .class 文件读进内存);
  • 将字节流的静态存储结构转化为运行时的数据结构;
  • 在内存中生成该类的 Class 对象;HotSpot 虚拟机把这个对象放在方法区,非 Java 堆。

获取二进制字节流:对于 Class 文件,虚拟机没有指明要从哪里获取、怎样获取。除了直接从编译好的 .class 文件中读取,还有以下几种方式:

  • 从 zip 包中读取,如 jar、war等
  • 从网络中获取,如 Applect
  • 通过动态代理计数生成代理类的二进制字节流
  • 由 JSP 文件生成对应的 Class 类
  • 从数据库中读取,如 有些中间件服务器可以选择把程序安装到数据库中来完成程序代码在集群间的分发。

“非数组类”与“数组类”加载比较:

  • 非数组类
    • 系统提供的引导类加载器
    • 用户自定义的类加载器(如重写一个类加载器的 loadClass() 方法)
  • 数组类
    • 不通过类加载器,由 Java 虚拟机直接创建
    • 创建动作由 newarray 指令触发,new 实际上触发了 [全类名] 对象的初始化
    • 规则
      • 数组元素是引用类型
        • 加载:递归加载其组件
        • 可见性:与引用类型一致
      • 数组元素是非引用类型
        • 加载:与引导类加载器关联
        • 可见性:public

注意事项

  • 虚拟机规范未规定 Class 对象的存储位置,对于 HotSpot 虚拟机而言,Class 对象比较特殊,它虽然是对象,但存放在方法区中。
  • 加载阶段与连接阶段的部分内容交叉进行,加载阶段尚未完成,连接阶段可能已经开始了。但这两个阶段的开始实践仍然保持着固定的先后顺序。

2、验证

验证的目的:验证阶段确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

4 个验证过程:

  • 文件格式验证:是否符合 Class 文件格式规范,验证文件开头 4 个字节是不是 “魔数” 0xCAFEBABE
  • 元数据验证:保证字节码描述信息符号 Java 规范(语义分析)
  • 字节码验证:程序语义、逻辑是否正确(通过数据流、控制流分析)
  • 符号引用验证:对类自身以外的信息(常量池中的符号引用)进行匹配性校验

注意:这个操作虽然重要,但不是必要的,可以通过 -Xverify:none 关掉,可以缩短虚拟机类加载的时间

3、准备

描述:准备阶段是正式为类变量(或称“静态成员变量”)分配内存并设置初始值的阶段。这些变量(不包括实例变量)所使用的内存都在方法区中进行分配。初始值“通常情况下”是数据类型的零值(0, null...)

 

静态成员变量准备后的初始值:

  • public static int value = 123;
    • 准备后为 0,value 的赋值指令 putstatic 会被放在 <client>() 方法中,<client>()方法会在初始化时执行,也就是说,value 变量只有在初始化后才等于 123。
  • public static final int value = 123;
    • 准备后为 123,因为被 static final 赋值之后 value 就不能再修改了,所以在这里进行了赋值之后,之后不可能再出现赋值操作,所以可以直接在准备阶段就把 value 的值初始化好。

4、解析

描述: 将常量池中的 “符号引用” 替换为 “直接引用”。

  • 在此之前,常量池中的引用是不一定存在的,解析过之后,可以保证常量池中的引用在内存中一定存在。
  • 什么是 “符号引用” 和 “直接引用” ?
    • 符号引用:以一组符号描述所引用的对象(如对象的全类名),引用的目标不一定存在于内存中。
    • 直接引用:直接指向被引用目标在内存中的位置的指针等,也就是说,引用的目标一定存在于内存中。

5、初始化

描述: 类初始化阶段是类加载过程的最后一步,是执行类构造器 <clinit>() 方法的过程。

<client>() 方法:

  • 包含的内容:
    • 所有 static 的赋值操作;
    • static 块中的语句;
  • <client>() 方法中的语句顺序:
    • 基本按照语句在源文件中出现的顺序排列;
    • 静态语句块只能访问定义在它前面的变量,定义在它后面的变量,可以赋值,但不能访问。
  • 与 <init>() 的不同:
    • 不需要显示调用父类的 <client>() 方法;
    • 虚拟机保证在子类的 <client>() 方法执行前,父类的 <client>() 方法一定执行完毕。
      • 也就是说,父类的 static 块和 static 字段的赋值操作是要先于子类的。
  • 接口与类的不同:
    • 执行子接口的 <client>() 方法前不需要先执行父接口的 <client>() 方法(除非用到了父接口中定义的 public static final 变量);
  • 执行过程中加锁:
    • 同一时刻只能有一个线程在执行 <client>() 方法,因为虚拟机要保证在同一个类加载器下,一个类只被加载一次。
  • 非必要性:
    • 一个类如果没有任何 static 的内容就不需要执行 <client>() 方法。

注:初始化时,才真正开始执行类中定义的 Java 代码。

五、类加载器

1、如何判断两个类 “相等”

任意一个类,都由加载它的类加载器和这个类本身一同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都有一个独立的类名称空间。

  • “相等” 的要求
    • 同一个 .class 文件
    • 被同一个虚拟机加载
    • 被同一个类加载器加载
  • 判断 “相等” 的方法
    • instanceof 关键字
    • Class 对象中的方法:
      • equals()
      • isInstance()
      • isAssignableFrom()

2、类加载器的分类

类加载器
  • 启动类加载器(Bootstrap ClassLoader): 负责将存放在 <JAVA_HOME>\lib 目录中的,并且能被虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。
  • 扩展类加载器(Extension ClassLoader): 负责加载 <JAVA_HOME>\lib\ext 目录中的所有类库,开发者可以直接使用扩展类加载器。
  • 应用程序类加载器(Application ClassLoader): 由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,所以一般也称它为“系统类加载器”。它负责加载用户类路径(classpath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

当然,如果有必要,还可以加入自己定义的类加载器。

3、双亲委派模型

双亲委派模型:双亲委派模型是描述类加载器之间的层次关系。它要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。(父子关系一般不会以继承的关系实现,而是以组合关系来复用父加载器的代码)

工作过程:

  • 当前类加载器收到类加载的请求后,先不自己尝试加载类,而是先将请求委派给父类加载器
    • 因此,所有的类加载请求,都会先被传送到启动类加载器
  • 只有当父类加载器加载失败时,当前类加载器才会尝试自己去自己负责的区域加载

实现:

  • 检查该类是否已经被加载
  • 将类加载请求委派给父类
    • 如果父类加载器为 null,默认使用启动类加载器
    • parent.loadClass(name, false)
  • 当父类加载器加载失败时
    • catch ClassNotFoundException 但不做任何处理
    • 调用自己的 findClass() 去加载
      • 我们在实现自己的类加载器时只需要 extends ClassLoader,然后重写 findClass() 方法而不是 loadClass() 方法,这样就不用重写 loadClass() 中的双亲委派机制了

优点:

像 java.lang.Object 这些存放在 rt.jar 中的类,无论使用哪个类加载器加载,最终都会委派给最顶端的启动类加载器加载,从而使得不同加载器加载的 Object 类都是同一个。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为 java.lang.Object 的类,并放在 classpath 下,那么系统将会出现多个不同的 Object 类,Java 类型体系中最基础的行为也就无法保证。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

标签:初始化,Java,虚拟机,static,引用,Class,加载
来源: https://blog.csdn.net/qq_37776015/article/details/93928695

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有