JVM入门笔记

发布时间 2023-10-16 11:52:07作者: 白豆五

1. JVM介绍


Java虚拟机(Java Virtual Machine 简称JVM)是运行所有Java程序的抽象计算机,是Java编程语言的运行环境,它是Java最具吸引力的特性之一。

JVM本质上是一个运行在计算机上的程序,他的职责是运行Java字节码文件,这就是Java跨平台的本质原因。由于Java是开放的,有越来越多的编程语言开发的程序也可以运行在JVM之上,例如 大数据开发的Scala、安卓开发的Kotlin以及脚本语言Grovvy等。

image-20231015103906497


JVM的三大核心功能:

  • 解释执行虚拟指令:对字节码文件中的指令,实时的解释成机器码,让计算机执行;
  • 内存管理:Java虚拟机会自动为对象、方法等分配内存,并且内部提供了垃圾回收机制,会自动将内存中没有使用的对象回收掉,不像C/C++需要手动释放内存;
  • 即时编译(JIT):对热点代码进行优化(提前预热),提升执行效率,最终能达到接近C/C++语言的运行性能,甚至在特定场景下实现超越;

市面上常见的JVM:

名称 作者 支持版本 社区活跃度(star数) 特性 适用场景
HotSpot(Oracle JDK版) Oracle 所有版本 高(闭源) 使用最广泛,稳定可靠,社区活跃,JIT支持,OracleJDK默认虚拟机 默认
HotSpot(open JDK版) Oracle 所有版本 中(开源) 同上,OpenJDK默认虚拟机 默认,对DK有二次开发需求
GraalVM Oracle 11,17,19,企业支持 高(开源) 多语言支持,高性能、JIT、AOT支持 微服务、云原生架构,需要多语言混合编程
DragonwellJDK龙井 Alibaba 标准版8,11,17
扩展版11,17
低(开源) 对OpenJDK进行增强,而且高性能、bug修复、安全性提升,提供了JWarmup、ElasticHeap、Wisp特性支持 电商、物流、金融领域,对性能要求比较高
Eclipse OpenJ9(原BMJ9) IBM 8,11,17,19,20 低(开源) 高性能、可扩展、JIT、AOT特性支持 微服务、云原生架

Java虚拟机规范:由Oracle制定,它定义了Java虚拟机在设计和实现使需要遵守的一系列规范。主要包含clss字节码文件格式、类和接口的加载和初始化、指令集、异常处理、内存模型等内容。(不仅支持)

文档地址:https://docs.oracle.com/javase/specs/index.html

HotSpot虚拟机发展史:

image-20231015111803363

查看jdk自带的虚拟机:java -version

image-20231015111048925


2. 字节码文件详解


2.1 Java虚拟机的组成

JVM的组成部分:

  • 类加载器(Class Loader)

  • 运行时数据区域(Runtime Data Areas)

  • 执行引擎(Execution Engine)

  • 本地接口(Java Native Interface,JNI)

image-20231015154320760


2.2 字节码文件的组成

Java字节码以二进制的形式存储在.class文件中,无法直接用记事本查看,需要借助反编译工具。

jclasslib下载地址:https://github.com/ingokegel/jclasslib/releases/

image-20231015164010074

字节码文件主要由5部分组成:

  • 基本信息:魔数、字节码文件对应的Java版本号、访问标识(public final等等),父类和接口。

  • 常量池:保存了字符串常量、类或接口名、字段名主要在字节码指令中使用。

  • 字段:当前类或接口声明的字段信息。

  • 方法:当前类或接口声明的方法信息,所对应的字节码指令。

  • 属性:类的属性,比如源码的文件、内部类的列表等。


2.2.1 常量池

  • 字节码文件中常量池的作用:避免相同的内容重复定义,节省空间。

  • 常量池中的每个数据都有编号,编号从1开始。在字段或者字节码指令中通过编号可以快速的找到对应的数据。

  • 字节码指令中通过编号引用到常量池的过程称之为符号引用

image-20231015180554489

示例:在类字段上定义两个内容相同的常量

package cn.z3inc.demo;

/**
 * 测试字节码文件常量池
 *
 * @author 白豆五
 * @date 2023/10/15
 * @since JDK8
 */
public class ConstantPoolTest {

    public static final String STR1 = "人因梦想而伟大";
    public static final String STR2 = "人因梦想而伟大";

    public static void main(String[] args) {
        ConstantPoolTest constantPoolTest = new ConstantPoolTest();
    }
}

使用jclasslib工具查看该类的字节码文件:

image-20231015174525906

image-20231015174536552

2023-10-15 17 52 08


2.2.2 方法

(1) 字节码中的方法区域是存放字节码指令的核心位置,字节码指令的内容存放在方法的Code属性中。

image-20231015181845246

(2) 操作数栈是临时存放数据的地方,局部变量表是存放方法中的局部变量的位置。

累加的执行流程:

image-20231015192209577

字节码指令大概执行流程:

  1. iconst_0:将常量0放入操作数栈
  2. istore_1:把常量0从操作数栈中弹出,放入局部变量表1号位置。(此时操作数栈中不会有常量0)
  3. iload_1:将局部变量表1号位置的数据加载到操作数栈中。(把局部变量表中1号位置的数据复制一份放入操作数栈中)
  4. iconst_1:将常量1放入操作数栈(此时操作数栈中的数据为:0,1)
  5. iadd:将操作数栈顶部的两个数据进行累加,结果放入栈中(此时操作数栈中的数据为:1)
  6. istore_2:把累加结果从操作数栈中弹出,放入局部变量表2号位置。
  7. return:方法结束,返回

i++的执行流程:

image-20231015193642729

字节码指令大概执行流程:

  1. iconst_0:将常量0放入操作数栈
  2. istore_1:把常量0从操作数栈中弹出,放入局部变量表1号位置。
  3. iload_1:将局部变量表1号位置的数据加载到操作数栈中。
  4. iinc 1 by 1:将局部变量表中1号位置的数据增加1。(此时i为1)
  5. istore_1:把常量0从操作数栈中弹出,放入局部变量表1号位置。(数据会被覆盖,最终i为0)
  6. return:方法结束,返回

++i的执行流程:(字节码指令条数越多,操作性能会比较低些)

//源码
public static void main(String[] args) {
    int i = 0;
    i = i++;
}
iconst_0
istore_1
iinc 1 by 1  //在++i中inc在load指令之前执行
iload_1
istore_1
return

最终i的结果为:1。


2.2.3 常用的字节码工具

① javap -v命令:

javap是JDK自带的反编译工具,可以通过控制台查看字节码文件的内容。适合在服务器上查看字节码文件
内容。

命令行输入javap,查看javap命令的所有参数:

 -help  --help  -?        输出此用法消息
  -version                 版本信息
  -v  -verbose             输出附加信息
  -l                       输出行号和本地变量表
  -public                  仅显示公共类和成员
  -protected               显示受保护的/公共类和成员
  -package                 显示程序包/受保护的/公共类
                           和成员 (默认)
  -p  -private             显示所有类和成员
  -c                       对代码进行反汇编
  -s                       输出内部类型签名
  -sysinfo                 显示正在处理的类的
                           系统信息 (路径, 大小, 日期, MD5 散列)
  -constants               显示最终常量
  -classpath <path>        指定查找用户类文件的位置
  -cp <path>               指定查找用户类文件的位置
  -bootclasspath <path>    覆盖引导类文件的位置

查看字节码文件的内容信息:java -v 字节码文件名称(如果class文件在层级比较深的路径下,需要加上全路径名)

 E:\Code\Other\jiuye\java-demo\jvm-demo\jvm01-base\target\classes\cn\z3inc\demo 的目录

2023/10/15  19:29    <DIR>          .
2023/10/15  18:24    <DIR>          ..
2023/10/15  18:24               546 ConstantPoolTest.class
2023/10/15  19:29               419 Demo2.class
               2 个文件            965 字节
               2 个目录 148,872,474,624 可用字节

E:\Code\Other\jiuye\java-demo\jvm-demo\jvm01-base\target\classes\cn\z3inc\demo>javap -v Demo2.class
Classfile /E:/Code/Other/jiuye/java-demo/jvm-demo/jvm01-base/target/classes/cn/z3inc/demo/Demo2.class
  Last modified 2023-10-15; size 419 bytes
  MD5 checksum 2d530172474d18c31f88a970efaa624a
  Compiled from "Demo2.java"
public class cn.z3inc.demo.Demo2
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#19         // java/lang/Object."<init>":()V
   #2 = Class              #20            // cn/z3inc/demo/Demo2
   #3 = Class              #21            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               LocalVariableTable
   #9 = Utf8               this
  #10 = Utf8               Lcn/z3inc/demo/Demo2;
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               args
  #14 = Utf8               [Ljava/lang/String;
  #15 = Utf8               i
  #16 = Utf8               I
  #17 = Utf8               SourceFile
  #18 = Utf8               Demo2.java
  #19 = NameAndType        #4:#5          // "<init>":()V
  #20 = Utf8               cn/z3inc/demo/Demo2
  #21 = Utf8               java/lang/Object
{
  public cn.z3inc.demo.Demo2();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcn/z3inc/demo/Demo2;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: iinc          1, 1
         6: istore_1
         7: return
      LineNumberTable:
        line 10: 0
        line 11: 2
        line 12: 7
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       8     0  args   [Ljava/lang/String;
            2       6     1     i   I
}
SourceFile: "Demo2.java"

image-20231015200414022

扩展命令,解压jar包命令:jar -xvf xxx.jar


② jclasslib插件:

jclasslib插件使用教程:https://www.bilibili.com/video/BV1Wd4y1M7tU

image-20231015220133137

查看字节码内容之前,需要编译源代码:

image-20231015220113877


③ Arthas: (Java应用诊断利器)

img

Arthas是一款线上监控诊断产品,通过全局视角实时查看应用load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,大大提升线上问题排查效率。

官网地址:https://arthas.aliyun.com/doc/

下载地址:https://arthas.aliyun.com/arthas-boot.jar

1、以jar包方式运行arthas:


2.3 类的生命周期

2.4 类加载器

3. JVM的内存区域


4. JVM的垃圾回收机制