JVM入门到实战(一)

JVM入门到实战(一)

文章介绍

本篇文章是整理自学的知识点进行记录,我自学的课程是黑马程序员的JVM课程。想学习的话可自行到课程学习。

JVM介绍

  • JVM 本质上是一个运行在计算机上的程序,他的职责是运行Java字节码文件。

  • 你编写的Java代码,经过编译后产生.class的字节码文件(包含字节码指令),然后再JVM上运行,由JVM解释成机器语码交给计算机执行

    image-20240703102503894

  • JVM的功能:

    1. 解释和运行: 对字节码文件中的指令, 实时的解释成机器码, 让计算机执行

    2. 内存管理: 自动为对象、方法等分配内存,自动垃圾回收机制回收不再使用的对象

    3. 即时编译:热点代码有的优化,提升效率(主要是为了支持跨平台特性)

      image-20240703103806909

  • JVM的组成:类加载器、运行时数据区域、执行引擎

    image-20240703104236935

字节码文件介绍

字节码文件的组成

image-20240703104900962

  1. 基本信息:包括魔数(CAFEBABE)、字节码文件的版本号(1.8对应51)、访问标识符、父类、接口等

    image-20240703111452167

  2. 常量池: 包括字符串常量、类和接口名、字段名主要在字节码指令中使用

    image-20240703111532857

  3. 字段:当前类或接口的字段信息

    image-20240703111559700

  4. 方法:当前类或接口声明的方法信息字节码指令

    image-20240703111919679

  5. 属性:类的属性,比如源码的文件名内部类类的列表等

    image-20240703111715204

字节码文件查看工具:

  1. jclasslib应用程序:点击[这里](Releases · ingokegel/jclasslib (github.com))前往下载,因为是在GitHub上面需要自己用点魔法
  2. jclasslib插件:Idea工具直接下载对应插件即可

类的生命周期

类的生命周期可以粗略分为5个阶段加载连接初始化使用卸载

细致可以分为7个阶段:将五个阶段中的连接 阶段,进行细分为三个阶段:验证准备解析

image-20240703145933770

加载阶段

  1. 加载(Loading)阶段第一步是类加载器根据类的全限定名通过不同的渠道以二进制流的方式获取字节码信息。 程序员可以使用Java代码拓展的不同的渠道。

  2. 类加载器在加载完类之后,Java虚拟机会将字节码中的信息保存到方法区中。生成一个InstanceKlass对象,保存类的所有信息,里边还包含实现特定功能比如多态的信息。

  3. Java虚拟机还会在堆中生成一份与方法区中数据类似的java.lang.Class对象。 作用是在Java代码中去获取类的信息以及存储静态字段的数据(JDK8及之后)。

    image-20240703150158978

连接阶段

验证

image-20240703150319207

连接(Linking)阶段的第一个环节是验证,验证的主要目的是检测Java字节码文件是否遵守了《Java虚拟机规 范》中的约束。这个阶段一般不需要程序员参与。主要包含如下四部分:

  1. 文件格式验证,比如文件是否以0xCAFEBABE开头,主次版本号是否满足当前Java虚拟机版本要求。

  2. 元信息验证,例如类必须有父类(super不能为空)。

    image-20240703150452624

  3. 验证程序执行指令的语义,比如方法内的指令执行中跳转到不正确的位置。

  4. 符号引用验证,例如是否访问了其他类中private的方法等。

image-20240703150633008

准备

注意:本章涉及到的内存结构只讨论JDK8及之后的版本,8之前的版本后续章节详述。
  1. 准备阶段为静态变量(static)分配内存并设置初始值。这个阶段只会分配初始值,并不会给赋值,赋值需要在初始化阶段才会操作

    image-20240703151327411

以下是一些类型的初始值:

image-20240703151452567

  1. final修饰的基本数据类型的静态变量,准备阶段直接会将代码中的值进行赋值。

    image-20240703151618334

解析

  1. 解析阶段主要是将常量池中的符号引用替换为直接引用(符号引用就是在字节码文件中使用编号来访问常量池中的内容)。

    image-20240703151747315

    直接引用不在使用编号,而是使用内存中地址进行访问具体的数据。

image-20240703151823456

初始化阶段

  • 初始化阶段会执行静态代码块中的代码,并为静态变量赋值。

  • 初始化阶段会执行字节码文件中clinit部分的字节码指令。

    image-20240703152007328

    字节码指令的过程如下:

    image-20240703152115240

  • 以下几种方式会导致类的初始化:

    1. 访问一个类的静态变量或者静态方法,注意变量是final修饰的并且等号右边是常量不会触发初始化。
    2. 调用Class.forName(String className)。
    3. new一个该类的对象时。
    4. 执行Main方法的当前类。
  • 下面有几个测试题目

    第一个:

    image-20240703162511288

    第二个:

    image-20240703162547642

  • clinit指令在特定情况下不会出现,比如:如下几种情况是不会进行初始化指令执行的。

    1. 无静态代码块且无静态变量赋值语句。

    2. 有静态变量的声明,但是没有赋值语句。

    3. 静态变量的定义使用final关键字,这类变量会在准备阶段直接进行初始化。

  • 直接访问父类的静态变量,不会触发子类的初始化。

  • 子类的初始化clinit调用之前,会先调用父类的clinit初始化方法。

    下面是例题:

    image-20240703162905582

    把new B02()去掉:

    image-20240703162943360

    另外两个关于初始化的练习题:

    第一题:

    image-20240703163501570

    第二题:

    image-20240703163525574