侧边栏壁纸
博主头像
Zhangsnke博主等级

这是Zhangxike的平凡生活!

  • 累计撰写 16 篇文章
  • 累计创建 11 个标签
  • 累计收到 15 条评论

目 录CONTENT

文章目录

回顾JVM(一)

Zhangsnke
2022-05-31 / 0 评论 / 0 点赞 / 361 阅读 / 4,821 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-06-13,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

前言:了解过Java的同学都知道Java的跨平台性,那么到底是什么来支撑这个跨平台性,还有经常被问到的内存和类加载过程具体是啥。什么是JVM,是么是运行时内存结构,怎么调优等等,这一切都是围绕着JVM这个来展开。这也是我第一次记录再次回顾JVM的过程。

虚拟机的基础概念

1.Java运行的过程

Java是怎么样从编码到执行的整个过程。
x.java -> 执行javac -> 变成 x.class ,当我们调用java命令的时候,class会被加载到内存中也叫作classLoader,一般情况下,我们自己写的类文件有时候会引用到java的类库,所以它也要把java类库相关的类加载到内存里,装载完成之后会调用字节码解释器或者即使编译器来进行解释或编译。编译完成之后需要由执行引擎来执行,执行引擎下面对应的就是操作系统的硬件,这一块内容就叫做JVM。
image-1653989112350

需要注意的是:解释和编译其实可以混合的。当一些常用的代码使用到的次数比较多的时候,一般会把一个即时的编译做成一个本地的编译,就像C语言在Windows上执行代码的时候把它编译成exe一样,那么下次再执行这段代码的时候就不要解释器来一句句解释执行。执行引擎可以直接交给操作系统去调用,这个效率会高很多。不是所有的代码都可以被JIT进行及时编译

2.JVM与Java无关

JVM可以称之为一个跨语言的平台,Java叫跨平台的语言,作为JVM虚拟机来讲目前能够在JVM上跑的语言很多,除了Java外还有Scala,kotlin,groovy,jruby等等。所谓的JVM虚拟机本身也是一种规范,Linux上有Linux的实现,Windows上有Windows的实现,Java virtual machine就是给你屏蔽了操作系统的这些底层。

Java虚拟机可以做到这么多语言跑起来,关键的原因就是class这个东西, 任何一种语言只要你能编译成class,符合class文件的规范,你就可以扔在JVM上执行。所以从JVM角度来讲,他不关心你是哪种语言,只关心你的class是否符合规范。

Java语言规范文档

3.常见的JVM实现

JVM其实是虚构出来的一个计算机,那它有自己的CPU,自己的指令集,汇编语言。
JVM也是一种规范,既然是一种规范就会有具体的实现,定义了一个接口就会有具体的实现类。Oracle就定义了Java虚拟机的实现标准。

以下是常见的JVM实现。

  • Hotspot
    Oracle官网的,目前用的最多的JVM。在命令行敲 java -version 会输出你现在用的虚拟机的名字,执行模式和版本信息等。 64位的server版,用的执行模式是 mixed mode(解释和即时执行混合)。
    image-1653993054366

  • Jrockit
    BEA 曾经号称世界上最快的JVM,被oracle收购,后合并于hotspot

  • J9
    IBM有自己的Java虚拟机实现,名字叫J9

  • Microsoft VM
    微软也有自己的实现

  • TaobaoVM
    某宝根据hotspot深度定制版

  • LiquidVM
    直接针对硬件的虚拟机VM,没有任何操作系统的,运行效率更高

  • azul zing
    最新垃圾回收的业界标杆 azul zing 这个是一个商业产品,很贵,但是有一个特点就是快,非常快,尤其是垃圾回收在1毫秒之内。它的一个垃圾回收算法被Hotspot吸收才有了现在的ZGC

4.JDK、JRE、JVM

JVM叫做Java虚拟机,只是来执行的,就是你所有的东西弄好了之后给他来执行就行。
JRE叫做运行时环境,Java想要在操作系统上运行,除了虚拟机外,还要有哪些Java的核心类库,如果没有这些核心类库是无法正常运行的。
JDK是Java开发工具包,包含了JRE和JVM。
image-1653994167919

Class 文件结构

整个class文件的格式就是一个二进制字节流,整个二进制字节流是由JVM来解释的。
下面这个是以16进制打开的class文件。

CA FE BA BE 00 00 00 34 00 12 0A 00 04 00 0E 09 00 03 00 0F 07 00 10 07 00 11 01 00 04 74 65 73 74 01 00 01 49 01 00 06 3C 69 6E 69 74 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 01 00 03 28 29 49 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 00 0A 48 65 6C 6C 6F 2E 6A 61 76 61 0C 00 07 00 08 0C 00 05 00 06 01 00 05 48 65 6C 6C 6F 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 00 21 00 03 00 04 00 00 00 01 00 02 00 05 00 06 00 00 00 02 00 01 00 07 00 08 00 01 00 09 00 00 00 1D 00 01 00 01 00 00 00 05 2A B7 00 01 B1 00 00 00 01 00 0A 00 00 00 06 00 01 00 00 00 01 00 01 00 05 00 0B 00 01 00 09 00 00 00 1D 00 01 00 01 00 00 00 05 2A B4 00 02 AC 00 00 00 01 00 0A 00 00 00 06 00 01 00 00 00 04 00 01 00 0C 00 00 00 02 00 0D

一般u1,u2,u3…u8,u指的是无符号整数。u1就是一个字节,把这一个字节看成无符号整数,u1就是一个字节的意思,所以 u2就是2个字节,u4就是4个字节,u8就是8个字节。

长度 类型 描述
u4 magic number 魔数
u2 minor version 小版本号
u2 major version 主版本号
u2 constant_pool_count 有多少个常量池
cp_info constant_pool[constant_pool_count-1] 常量池-1具体内容有很多种类型
u2 access_flags 指整个class访问权限是public还是private
u2 this_class 当前这个类是谁
u2 super_class 当前这个类的父类是谁
u2 interfaces_count 实现了哪些接口
if_info interfaces[interfaces_count] 接口的索引
u2 fields_count 属性个数
fields_info fields[fields-count] 具体的属性
u2 methods_count 方法的个数
methods_info methods[methods_count] 方法的具体内容,包含各种结构
u2 attributes_count 附加属性个数
attribute_info attributes[arrtibutes_count] 附加属性的具体内容

1.Magic Number

最前面的四个字节,指的是这个文件的统一标识符,当我们看到这个文件的统一标识符的时候没就知道这个文件是class文件。很多文件都是这样来标识的,都有自己的头,用16进制打开,前几位一般都是一样的 比如 CA FE BA BE 就是Java编译完的class文件,四个字节,叫做magic number。

小插曲:CA FE BA BE 刚好就是cafe babe 。
CA FE DE AD 是持久对象的魔术,就和这个名字一样,cafe dead 持久化对象(persistent object)技术真的消失了, 后来用的是RMI技术。

2.Minor Version

2字节表示的整个版本的小标识符,比如00 00

3.Major Version

2字节表示的主要的版本标识 00 34,和 minor version一起组成了这个class文件的版本号。比如 JDK1.7 默认为51.0 JDK1.8为52.0。

4.Constant_Pool_Count

2字节表示常量池有多少个内容

5.Constant_pool

常量池是class文件最复杂的内容,而且常量池里会互相引用,以及常量池会被其他的各方面引用,所以常量池是极其复杂的。常量池是constant_pool_count-1长度的表,常量池是从1开始的,数组是从下标0开始的,所以要减去1。

从1开始的原因:有一个0存在里面,将来没准会有其他引用指向。所以留了一个0也表示了一个可能性。0不指向任何常量池里的一项。

  1. 几乎包含类所有信息的描述
  2. 是一个类的机构索引,其他地方对对象的引用通过索引位置来替代。
  3. 常量池中的数据也是一项一项,没有间隙的依次存放。常量池的各个数据项通过索引来访问,有点类似数组,只不过第一项的索引时1而不是0。如果class文件中的其他地方引用了索引为0的常量池项,就表示不引用任何的常量池项。

tag:1

常量 项目 长度 描述
CONSTANT_Utf8 tag u1 值为1
length u2 UTF-8字符串占用的字节数
bytes u-length 长度为length的UTF-8编码的字符串

tag:3

常量 项目 长度 描述
CONSTANT_Integer tag u1 值为3
bytes u4 按照高位在前Big-Endian存储int值

tag:4

常量 项目 长度 描述
CONTSTANT_Float tag u1 值为4
bytes u4 按照高位在前Big-Endian存储float值

tag:5

常量 项目 长度 描述
CONTSTANT_Long tag u1 值为5
bytes u8 按照高位在前Big-Endian存储long值

tag:6

常量 项目 长度 描述
CONTSTANT_Double tag u1 值为6
bytes u8 按照高位在前Big-Endian存储Double值

tag:7

常量 项目 长度 描述
CONTSTANT_Class tag u1 值为7
index u2 指向全限定名常量的索引

tag:8

常量 项目 长度 描述
CONTSTANT_String tag u1 值为8
index u2 指向字符串字面量的索引

tag:9

常量 项目 长度 描述
CONTSTANT_Fieldref tag u1 值为9
index u2 指向声明字段的类或接口描述符CONSTANT_Class_info的索引项
index u2 指向字段名称以及类型描述符CONSTANT_NameAndType_info的索引项

tag:10

常量 项目 长度 描述
CONTSTANT_Methodref tag u1 值为10
index u2 指向声明方法的类或接口描述符CONSTANT_Class_info的索引项
index u2 指向方法名称以及类型描述符CONSTANT_NameAndType_info的索引项

tag:11

常量 项目 长度 描述
CONTSTANT_InterfaceMethodref tag u1 值为11
index u2 指向声明方法的类或接口描述符CONSTANT_Class_info的索引项
index u2 指向方法名称以及类型描述符CONSTANT_NameAndType_info的索引项

tag:12

常量 项目 长度 描述
CONTSTANT_NameAndType tag u1 值为12
index u2 指向字段或方法名称常量项目的索引
index u2 指向该字段或方法描述符常量的索引

tag:15

常量 项目 长度 描述
CONTSTANT_MethodHandle tag u1 值为15
reference_kind u1 1-9之间的一个值,决定了方法句柄的类型,方法句柄类型的值表示该方法句柄的字节码行为
reference_index u2 对常量池有效索引

tag:16

常量 项目 长度 描述
CONTSTANT_MethodType tag u1 值为16
descriptor_index u2 指向Utf8_info结构表示方法的描述符

tag:18

常量 项目 长度 描述
CONTSTANT_InvokeDynamic tag u1 值为18
bootstrap_method-attr_index u2 当前class文件中引导方法表示的bootstrap_methods[]数组的有效索引
name_and_type_index u2 指向CONSTANT_NameAndType表示的方法名和方法描述的索引

6.Access_flag

保存了当前类的访问权限

标记名称 具体含义
ACC_PUBLIC 0x0001 public
ACC_FINAL 0x0010 final
ACC_SUPER 0x0020 用于兼容早期编译器。新编译器都设置该标记,已在使用invokespecial指令时对子类方法做特定处理。
ACC_INTERFACE 0x0200 接口,同时需要设置:ACC_ABSTRACT,不可同时设置:ACC_FINAL,ACC_SUPER,ACC_ENUM
ACC_ABSTRACT 0x0400 抽象类,无法实例化,不可与ACC_FINAL同时设置
ACC_SYNTHETIC 0x1000 synthetic ,由编译器产生,不存在源代码中
ACC_ANNOTATION 0x2000 注解类型(annotation)同时需要设置ACC_INTERFACE、ACC_ABSTRACT
ACC_ENUM 0x4000 枚举类型

7.This_Class

保存了当前类的全局限定名在常量池里的索引

8. Super_Class

保存了当前类的父类的全局限定名在常量池里的索引

9. Interfaces

保存了当前类实现的接口列表,包含了两部分

  • intertfaces_count
    指当前类实现的接口数目
  • interfaces[interface_count]
    包含interfaces_count个接口的全局限定名的索引的数组

10.Fields

  1. fields_count
    类变量和实例变量的字段的数量总和
  2. fields[fields_count]
    包含字段详细信息的列表
field_info{
	 u2 access_flags;    //保存了当前字段的访问权限
    u2 name_index;   //字段名称的索引
    u2 descriptor_index;  //字段的描述符索引
    u2 attributes_count;  //附加属性的个数
    attribute_info attributes[attributes_count];   //附加属性的数组
}

image-1654019585037

11.Methods

  1. methods_count
    该类或者接口显示定义方法的数量总和
  2. methods[methods_count]
    包含方法详细信息的列表
method_info{
	 u2 access_flags;    //保存了当前方法的访问权限
    u2 name_index;   //方法名称的索引
    u2 descriptor_index;  //方法的描述符索引
    u2 attributes_count;  //附加属性的个数
    attribute_info attributes[attributes_count];   //附加属性的数组
}

12.Attributes

  1. class文件里最后一部分是属性,描述了该类或者接口所定义的一些属性信息。
  2. 属性可以出现在class文件的很多地方,而不是只出现在attributes列表里
    • 如果是attributes表里的属性,那么他就是对整个class文件所对应的类或者接口的描述
    • 如果出现在fields的某一项里,那么就是对该字段额外信息的描述
    • 如果出现在methods的某一项里,那么就是对该方法额外信息的描述
  3. 包含两个部分
    • attributes_count
      attributes列表中包含的attribute_info的数量
    • attributes[attributes_count]
      包含属性信息的一个详细列表

解析二进制文件

image-1654064341092

cafe babe 0000 0034 001d 0a00 0600 0f09
0010 0011 0800 120a 0013 0014 0700 1507
0016 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 046d 6169
6e01 0016 285b 4c6a 6176 612f 6c61 6e67
2f53 7472 696e 673b 2956 0100 0a53 6f75
7263 6546 696c 6501 0012 4a61 7661 436c
6173 7356 6965 772e 6a61 7661 0c00 0700
0807 0017 0c00 1800 1901 0004 5465 7374
0700 1a0c 001b 001c 0100 0d4a 6176 6143
6c61 7373 5669 6577 0100 106a 6176 612f
6c61 6e67 2f4f 626a 6563 7401 0010 6a61
7661 2f6c 616e 672f 5379 7374 656d 0100
036f 7574 0100 154c 6a61 7661 2f69 6f2f
5072 696e 7453 7472 6561 6d3b 0100 136a
6176 612f 696f 2f50 7269 6e74 5374 7265
616d 0100 0770 7269 6e74 6c6e 0100 1528
4c6a 6176 612f 6c61 6e67 2f53 7472 696e
673b 2956 0021 0005 0006 0000 0000 0002
0001 0007 0008 0001 0009 0000 001d 0001
0001 0000 0005 2ab7 0001 b100 0000 0100
0a00 0000 0600 0100 0000 0700 0900 0b00
0c00 0100 0900 0000 2500 0200 0100 0000
09b2 0002 1203 b600 04b1 0000 0001 000a
0000 000a 0002 0000 000a 0008 000b 0001
000d 0000 0002 000e

image

如图上展示 依次列举完所有的常量还是比较麻烦的,这里也有一个简单的方法,利用java自带命令

javap -v x.class

image-1654059202595

0021 0005 0006 0000 0000 0002
0001 0007 0008 0001 0009 0000 001d 0001
0001 0000 0005 2ab7 0001 b100 0000 0100
0a00 0000 0600 0100 0000 0700 0900 0b00
0c00 0100 0900 0000 2500 0200 0100 0000
09b2 0002 1203 b600 04b1 0000 0001 000a
0000 000a 0002 0000 000a 0008 000b 0001
000d 0000 0002 000e

这是切分完magic number ,minor version 和 major version ,constant_pool_count 和constant_pool 所剩下的二进制数。

access_flags( u2 )

所以0021开头就是access_flags的。但是access_flags表里面无法直接查询到0021这个值,原因是0021=0020+0001,也就是表示当前class的access_flag是ACC_PUBLIC|ACC_SUPER。

ACC_PUBLIC:就是 代码里的 public 关键字相对应
ACC_SUPER:当用invokespecial指令来调用父类方法时需要特殊处理

this_class( u2 )

this_class 占2个字节,u2 -> 00 05

this_class 必须指向 constant_pool 里的 CONSTANT_Class_info 索引值
这里是5也就是常量池里的5 #5=Class JavaClassView

super_class (u2)

super_class 占2个字节,u2 -> 00 06

super_class 必须指向 constant_pool 里的 CONSTANT_Class_info 索引值
这里是6也就是常量池里的6 #6=Class java/lang/Object

interfaces(u2 u2)

interfaces 包含 u2的 interfaces_count 和 u2的interfaces[interfaces_count]
这里都是0000 表示没有实现接口方法 所以后面的u2也不存在了,不需要看。

fields(u2 field_info)

fields包含了 u2的fields_count 这里 00 00 标识成员变量个数为0个。

methods(u2 method_info)

methods 包含了 u2的methods_count
0002表示 method_count 个数为2,也就是方法有两个。但是明明只写了一个方法,为什么有两个?

这是因为JVM会自动生成一个方法,这个就是类的默认无参构造方法

这里继续回顾下 methods_info结构体

method_info{
	 u2 access_flags;    //保存了当前方法的访问权限
    u2 name_index;   //方法名称的索引
    u2 descriptor_index;  //方法的描述符索引
    u2 attributes_count;  //附加属性的个数
    attribute_info attributes[attributes_count];   //附加属性的数组
}

转换一下,把二进制数放进去

00 02 method_info
      00 01 access_flags
      00 07 name_index
      00 08 descriptor_index
      00 01 attributes_count
                 Code_attribute
                 00 09                   attribute_name_index
                 00 00 00 1d        attribute_length
                 00 01                   max_stack
                 00 01                   max_locals
                 00 00 00 05       code_length
                          2a b7 00 01 b1    code[code_length]
                 00 00                  exception_table_length 
                 00 01                  attributes_count
                           LineNumberTable_attribute
                           00 0a        attribute_name_index
                           00 00 00 06 attribute_length
                           00 01         line_number_table_length
                                     00 00     start_pc
                                     00 07     line_number                     

access_flags 为00 01 即 ACC_PUBLIC
name_index 为指向常量池的第7个常量,为
descriptor_index指向常量池的第8个常量为 V()
组合起来就是 public void ()。

接着看attribute字段,这个也是方法的附加属性,这里的attributes_count 0001代表有一个属性attribute_info结构。

  attribute_info {
  			u2 attribute_name_index;
            u4 attribute_length;
            u1  info[attribute_length];
  }

而attribute_name_index为00 09 指向常量池里的第九个常量为Code对应了Code Attribute的结构体。

Code_attribute {
     u2 attribute_name_index;    //这里为00 09 表示指向第9个常量,即为Code,对应结构 Code_attribute
     u4 attribute_length;  // attribute所包含字节数
     u2 max_stack;  //表示这个方法运行的任何时刻能达到最大操作数栈的最大深度
     u2 max_locals; //表示这个方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量
     u4 code_length;
     u1 code[code_length];
     u2 exception_table_length;  //这里存放是处理异常的信息
      {
             u2 start_pc;   // code数组从start_pc 到end_pc处 (包含start_pc,不包含end_pc)
             u2  end_pc;
             u2 handle_pc; // 表示处理异常的代码的开始处
             u2   catch_type; //表示会被处理的异常类型
      }  exception_table[exception_table_length];
      u2  attributes_count;
      attribute_info attributes[attributes_count];
  }
0

评论区