前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JVM - 类加载过程

JVM - 类加载过程

作者头像
用户2987604
发布2020-06-15 15:35:34
4600
发布2020-06-15 15:35:34
举报

当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。

字节码

在聊 Java 类加载机制之前,需要先了解一下 Java 字节码,因为它和类加载机制息息相关。

计算机只认识 0 和 1,所以任何语言编写的程序都需要编译成机器码才能被计算机理解,然后执行,Java 也不例外。

Java 在诞生的时候喊出了一个非常牛逼的口号:“Write Once, Run Anywhere”,为了达成这个目的,Sun 公司发布了许多可以在不同平台(Windows、Linux)上运行的 Java 虚拟机(JVM)——负责载入和执行 Java 编译后的字节码。

我们借助一段简单的代码来看一看,源码如下:

代码语言:javascript
复制
package org.blackist.jvm;public class JvmDemo {    public static void main(String[] args) {        System.out.println("董亮亮的开发笔记");    }}

代码编译过后,通过十六进制工具 xxd JvmDemo.class命令查看这个字节码文件:

代码语言:javascript
复制
00000000: cafe babe 0000 0034 0022 0a00 0600 1409  .......4."......00000010: 0015 0016 0800 170a 0018 0019 0700 1a07  ................00000020: 001b 0100 063c 696e 6974 3e01 0003 2829  .....<init>...()00000030: 5601 0004 436f 6465 0100 0f4c 696e 654e  V...Code...LineN00000040: 756d 6265 7254 6162 6c65 0100 124c 6f63  umberTable...Loc00000050: 616c 5661 7269 6162 6c65 5461 626c 6501  alVariableTable.

这段字节码中的 cafe babe 被称为“魔数”,是 JVM 识别 .class 文件的标志。文件格式的定制者可以自由选择魔数值(只要没用过),比如说 .png 文件的魔数是 89504e47

类加载过程

JVM结束生命周期的几种情况:

  • 执行了System.exit()方法
  • 程序正常执行结束
  • 程序执行过程中遇到异常或错误而异常终止
  • 操作系统出现错误而导致JVM进程终止

Java 的类加载过程可以分为 5 个阶段:载入、验证、准备、解析和初始化。这 5 个阶段一般是顺序发生的,但在动态绑定的情况下,解析阶段发生在初始化阶段之后。

加载

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

将类的.class文件中的二进制数据读入到内存,将其放在运行时数据区的方法区内,然后在堆去创建java.lang.Class对象,用来封装类在方法区内的数据结构。

加载.class的方式
  • 从本地加载
  • 从网络上加载(URLClassLoader(URL[] urls))
  • 从zip, jar等归档文件中加载.class文件
  • 从专有数据库提取.class文件
  • 将Java源文件动态编译成.class文件

连接

验证

确保被加载的类的正确性,符合JVM字节码规范,该阶段是保证 JVM 安全的重要屏障,下面是一些主要的检查。

  • 确保二进制字节流格式符合预期(比如说是否以 cafe bene 开头)。
  • 是否所有方法都遵守访问控制关键字的限定。
  • 方法调用的参数个数和类型是否正确。
  • 确保变量在使用之前被正确初始化了。
  • 检查变量是否被赋予恰当类型的值。
准备

JVM 会在该阶段对类变量(也称为静态变量, static 关键字修饰的)分配内存并初始化(对应数据类型的默认初始值,如 0、0L、null、false 等)。

代码语言:javascript
复制
public String blackist = "Blackist";public static String note = "Note";public static final String bnote = "Note-of-Blackist";

blackist不会被分配内存,而 note会;但 bnote的初始值不是“王二”而是 null

需要注意的是, staticfinal 修饰的变量被称作为常量,和类变量不同。常量一旦赋值就不会改变了,所以 bnote 在准备阶段的值为“沉默王二”而不是 null

解析

该阶段将常量池中的符号引用转化为直接引用。

符号引用以一组符号(任何形式的字面量,只要在使用时能够无歧义的定位到目标即可)来描述所引用的目标。

在编译时,Java 类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如 org.blackist.Quiz 类引用了 org.blackist.Bnote类,编译时 Quiz类并不知道 Bnote类的实际内存地址,因此只能使用符号 org.blackist.Bnote

直接引用通过对符号引用进行解析,找到引用的实际内存地址。

初始化

类变量已经被赋过默认初始值,而在初始化阶段为类的静态变量赋予正确的初始值。

代码语言:javascript
复制
public class Test {    // 准备阶段默认值为0,初始化阶段赋值3    private static int foo = 3;    // 也可写为    peivate static int foo;    static {        foo = 3;    }    // 静态代码块从上到下顺序执行,foo最终等于4    static {        foo = 4;    }}

换句话说,初始化阶段是执行类构造器方法的过程。

Java程序对类的使用

主动使用(六种)

  • 创建类的实例(new Test();)
  • 访问某个类的或接口的静态变量,或对该静态变量赋值(int b = Test.a; Test.a = b;)
  • 调用类的静态方法 (Test.foo())
  • 反射(ClassForName("org.blackst.demo.Quiz"))
  • 初始化类的子类
代码语言:javascript
复制
class Parent {}class Child extends Parent {    public static int a = 3;}// 初始化子类,对父类进行了主动使用Child.a = 4;
  • JVM启动时被标为启动类的类(如JavaTest,java org.blackist.Test)

所有JVM实现必须在每个类或接口被Java程序 首次主动使用 时才初始化。

被动使用

除了主动使用以外的使用,都不会导致类的初始化。

示例

如下程序:

输出:

代码语言:javascript
复制
10

准备阶段:singleton=null, counter1=0, counter2=0

主动调用:Singleton.getInstance()触发主动调用,进行初始化

初始化阶段:singleton=new Singleton()调用构造方法[counter1=1,counter2=1],counter1不变,counter2=0

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 董亮亮的开发笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 字节码
  • 类加载过程
    • 加载
      • 加载.class的方式
    • 连接
      • 验证
      • 准备
      • 解析
    • 初始化
    • Java程序对类的使用
      • 主动使用(六种)
        • 被动使用
        • 示例
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档