JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用

简介: 这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。

前言

  1. 文中所用到的class文件结构思维导图下载:class文件思维导图(这个思维导图的来源是下面 的 jvm class 文件格式 官网
  2. jvm 13版本 规范 HTML 版本:https://docs.oracle.com/javase/specs/jvms/se19/html/index.html
  3. java 各版本和 JVM各版本下载:https://docs.oracle.com/javase/specs/index.html
  4. 本博文是以jdk8版本学习的,文档是13版本。
  5. 本博客是讲:
    • JVM的一些基础、跨平台的语言和跨语言的平台。
    • 字节码 Class 文件(Class File Format) 的十六进制的布局、含义,看class文件的几个工具。
  6. 下个博文讲,class文件如何加载(load)到内存以及加载的。
  7. 知识体系分布:
    1. JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
    2. JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制
    3. JVM知识体系学习三:class文件初始化过程、硬件层数据一致性(硬件层)、缓存行、指令乱序执行问题、如何保证不乱序(volatile等)
    4. JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配

一、JVM基础

1、cross platform 跨平台

即任何语言只要编译成 class 文件,在装有JVM任何的系统上 都可以运行。

在这里插入图片描述

2、cross language 跨语言

即:有好多语言是在JVM上运行的,这就是夸语言的。
在这里插入图片描述

3、什么是JVM呢?一张图告诉你

在这里插入图片描述

4、java从编码到执行*****

  1. 通过 javac 命令:将 java文件 编译成 class 文件
  2. 通过 java 命令:classloader 加载 class 文件以及 java 类库 到内存中进行装载,装载完成后, 然后调用 字节码解释器 或者JIT即时编译器 来进行解释 或者 编译。
  3. 之后 由 执行引擎 进行 执行,最终到OS操作系统。
  • 那java是解释执行的还是编译执行的呢?其实 解释和编译是可以混合的,如果代码用到的次数比较多,会把代码做成 即时编译 即做成本地的编译,就类似c语言在win中编译成exe文件,这样效率会比较高。
    在这里插入图片描述

5. 从跨平台的语言到跨语言的平台

JVM:是跨语言的平台。
java:是跨平台的语言。
在JVM上能够跑的语言 到目前有100多种,比如下图中的Scala、groovy语言等。
在这里插入图片描述

6. jvm与class文件格式

== jvm跟java无关==
在这里插入图片描述

7. JVM

  • jvm是一种规范 – java virtual machine specifications
    • https://docs.oracle.com/en/java/javase/13/
    • https://docs.oracle.com/javase/specs/index.html
  • JVM 是 虚构出来的一台计算机
    • 字节码指令集(汇编语言)
    • 内存管理:栈 堆 方法区等

白话解释:
JVM是一台虚拟出来的一台机器,也就有自己的CPU、内存管理,比如栈、堆、方法区,所以也就有后面的JVM调优等等。

8. javac的过程

在这里插入图片描述

9. 常见的JVM实现

  1. Hotspot

    • oracle官方,我们做实验用的 JVM
    • java –version
  2. Jrockit

    • BEA,曾经号称世界上最快的 JVM
    • 被Oracle收购,合并于hotspot
  3. J9 – IBM

  4. Microsoft VM

  5. TaobaoVM

    • hotspot深度定制版 ▪
  6. LiquidVM

    • 直接针对硬件 ▪
  7. azul zing

    • 最新垃圾回收的业界标杆
    • www.azul.com

在这里插入图片描述
Hotspot:就是上面所说的第一个JVM类型
mixed mode:就是 上面 1.4说的混合模式,解释和编译混合执行。

10. JDK JRE JVM

  • jdk全称:java development kit,其意思是java开发工具包。jdk是sun公司开发的,jdk包括jre(java runtime environment)java运行环境,一堆java工具[java的编译器(java c.exe),java解释执行器(java.exe)]和java基础的类库(有3000多类,常用的类150多个)。

  • JRE(Java Runtimely Environment),java运行环境,只能运行.class文件,不能编译,针对用户。JRE,包含一个JVM(java虚拟机),与java核心类库与其所支持的文件。与JDK不同,它不包含开发工具—编译器,调试器和其他工具。

  • JVM(java Virtual Machine ) ,Java虚拟机,Java运行环境。Java虚拟机,是一种虚拟出来的计算机,是通过在实际的计算机上模拟仿真各种计算机功能来实现的。

在这里插入图片描述

二、Class File Format (class 文件格式)

分析和学习class文件。目前公司面试很少用到,但是需要学习和了解哈,抱着兴趣去学。

不能抱着功利性去学。
class文件:就是编译完成之后的 .class 文件

1、测试小程序

a、T0100_ByteCode01.java

最简单的测试小程序,就是一个类,是为了方便观察其编译后的内容,然后由简到繁,逐步学习。

package com.mashibing.jvm.c1_bytecode;

public class T0100_ByteCode01 {
}

然后编译,生成 T0100_ByteCode01.class 文件。

b、idea打开T0100_ByteCode01.class

如果在idea中打开编译后的 T0100_ByteCode01.class 的文件,就是idea会帮我们进行反编译,反编译的多了一个默认的无参构造函数,这是默认添加的。
注释是反编译出来的注释

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.mashibing.jvm.c1_bytecode;

public class T0100_ByteCode01 {
    public T0100_ByteCode01() {
    }
}

2、class文件

  • 任何的文件都应该是010101二进制,class文件如果用一个16进制编辑器(sublime text 3)打开,就是如下图所示:
    在这里插入图片描述

  • class文件是 二进制字节流。

  • 数据类型:u1 u2 u4 u8 和 _info (表类型)(只是逻辑上分的,其实没有数据类型,只有0和1 )(u:Unsigned,意为为无符号的,u1指1个字节;u2指2个字节;u3指3个字节;u4指4个字节;u8指8个字节

    • _info 的来源 是Hotspot 源码中的写法
  • 查看16进制格式classFile 的工具

    • sublime(打开如上图) / notepad

    • idea插件:Bined
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      (上面的红框可以查看二进制、八进制、十六进制的文件,当然二进制是最根本的文件格式)

  • 有很多可以观察byteCode的方法

    1. javap(java自带):下面有使用
    2. JBE 可以直接修改
    3. JclassLib idea插件之一 (下面以这个工具主要讲解)
      在这里插入图片描述
      未下载前 是idea自带的
      将鼠标放在class文件或者Java文件中:
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      不好观察,所以下载插件: JclassLib
      将鼠标放在class文件或者Java文件中:
      在这里插入图片描述
      在这里插入图片描述
  • classfile构成
    看博文开头的xmind文件。

    ClassFile {
        u4             magic;
        u2             minor_version;
        u2             major_version;
        u2             constant_pool_count;
        cp_info        constant_pool[constant_pool_count-1];
        u2             access_flags;
        u2             this_class;
        u2             super_class;
        u2             interfaces_count;
        u2             interfaces[interfaces_count];
        u2             fields_count;
        field_info     fields[fields_count];
        u2             methods_count;
        method_info    methods[methods_count];
        u2             attributes_count;
        attribute_info attributes[attributes_count];
    }
    

3、Class文件解读

  1. class就是二进制字节流,那怎么解释呢,看由谁来解释,这里是由java虚拟机 JVM 来解释的,为了方便下面以16进制进行查看二进制字节流,十六进制也是 jvm 的规范进行解读。

  2. 每个十六进制 对应 jvm 制定规范的 含义和指令。比如下面的十六进制 CAFE BABE 对应就是magic number ,含义就是class文件的开头,

  3. 提前剧透:常量池是class文件里最复杂的部分

下图就是上面2.2 第一个图片,编译后的class文件,对其进行解读。
在这里插入图片描述
== 讲解上图:==

一个小格是一个字节。对十六进制来讲,一个十六进制就是4位。两个十六进制就是一个字节 即8位。(如下图可看出,第一个小格是CA,这是16进制,所以这是一个字节,即8位。)(1字节可以包含两个16进制数

  • CAFE BABE 这是java编译后class文件的抬头,比如其他png、GIF的都有自己独有的抬头。这就是 magic number 魔法数。

  • 0000 0034 中,

    • 0000minor version ,小版本号,比如版本是52.0,则minor version就是.0的概念
    • 0034major version ,大版本号,十进制是52,1.8编译完后就是52,1.9就是53。
  • 1110constant_pool_count,常量池里存在常量的个数;两个字节,216=65536,,最多可放65535个常量最多是 - 1。

  • 接着是常量的表constant_pool :这里就是存常量池的地方,这里面放的是类的一些信息,比如类名、方法、参数等等。如xmid文档,截图部分如下图。

    其长度为constant_pool_count - 1的表;这里是10,十六进制 就是15,则 就是15-1=14个常量,为什么要减一,因为常量池数组是从1开始的(平时数组是从0开始)因为最前面保留了一个0,将来可能有一些引用指向会表示不指向任意常量的任何一项,就可以用0来代表。
    在这里插入图片描述

  • access flags :访问标识,比如public、private、protect、final等。
    在这里插入图片描述

  • 后面的依次进行 翻译,这里就略过,每个十六进制数 都对应jvm的设置好的含义或者是指令可以主要看博文最开始的 xmind文件以及jvm class 格式 官网

a、javap 翻译class 文件 (java自带)

这样看不是很清楚,可以通过工具来很清晰的查看,就是java自带的javap:会把class文件中的内容帮我们翻译好。(从2.2中可以看到)
javap T0100_ByteCode01.class:显示内容较少,如下,(可通过javap查看参数)
在这里插入图片描述
javap -v T0100_ByteCode01.class:下图中可以看到帮我们翻译出来的 minor version、major version、flags 等名称。
在这里插入图片描述
flags: (0x0021) ACC_PUBLIC, ACC_SUPER:后面的 ACC_SUPER 就是:该标志必须为真,JDK1.0.2之后编译出来的内容必须为真,指明invokespectial指令使用新语义

b、jclasslib 翻译class文件(idea插件)

使用 jclasslib 打开 class 文件,下面并进行分析和说明

i、一般信息

这里包含了class 文件结构的大多数基础信息,当然最重要的还是常量池。
在这里插入图片描述

  1. 本类索引,就是 this class :cp_info #7 <com/mashibing/jvm/c1_bytecode/T0100_ByteCode01>:后面的 <com/mashibing/jvm/c1_bytecode/T0100_ByteCode01> 就是本类的名称,cp_info #7 就是在常量池的7号存的。绿色可以点击,点击过去就是 常量池的7号位置。
  2. 父类索引,就是 super class:cp_info #2 <java/lang/Object> :后面的还是父类名称,前面就是父类存储在常量池的2号位置。
  3. 接口计数 即 interfaces count。
  4. 字段计数 即 fields count。
  5. 方法计数 即 methods count。
  6. 属性计数 即 attributes count。
ii、常量池:

在这里插入图片描述

  • 常量类型有很多种,如下图所示1,3,4,5,6到18,但是没有2,标记:常量池的每一种类型前边都有一个一个字节的标记,用的最多的是第一个:CONSTANT_Utf8_info ,代表 utf8的字符串。 (下图的思维导图可从文章开头的前言中找到并下载
    在这里插入图片描述
    在这里插入图片描述
  • jclasslib 打开的常量池 1号位置Methodref_info 文件:存的是方法引用信息,从思维导图中可以看出存的如下图所示,包括三个 1是标记10,2是index2个字节指向其他常量池 ,3是index2个字节指向其他常量池。
    在这里插入图片描述
  • 1号位置 methodref_ref中的 类名 cp_info #2 :意思是指向类的名字在2号位置,这里又存在了4号位置。
    在这里插入图片描述
    在这里插入图片描述
  • 回过头来再看 1号位置 methodref_ref 中的 描述 cp_info #3 :指向的是3号位置的name和type。可以看出
    <init>: 是构造函数
    <()v>()是指 没有参数,V是指返回类型为void类型。
    在这里插入图片描述
iii、接口、字段

因为2.1测试小程序很简单,没有接口和字段,所以这里为空
在这里插入图片描述
在一般信息中,也可以看出个数,如下
在这里插入图片描述

iv、方法*****

测试小程序虽然很简单,啥都没有
但是生成一个默认的 构造函数,调用的父类也就是java.lang.Obeject的构造方法
这里也有最重要的code 环节,因为函数里会有code的哈,这里会有大量的指令集(JVM大约定义256个左右)
在这里插入图片描述

  • <init> 就是默认的构造函数,从指令的右边可以看出 调用的是父类的默认的构造方法。

  • code 就是重要的代码部分,右边的上面也有指向常量池的类名称等信息,从这里也可以看出,类方法信息也都存在了常量池中。

  • JVM 13 版本 的第七章,是十六进制码 与 指令的 对照。即 十六进制码 代表了什么意思,方法的具体实现翻译成class文件都变成了第七章(下图)的 十六进制(本质二进制)和指令(十六进制的映射),也就是 java的汇编语言
    在这里插入图片描述

  • 当前在字节码中,有三个 指令。
    指令的翻译可以从官网中查找对应的意思。

    1. 第一个是 aload_0通过上面文档可以找到(也可以通过鼠标在 jclasslib 中点进去如下图),
      在这里插入图片描述
      aload_0 对应的是 0X2a 这个十六进制,可以在class文件的十六进制文本中找到 2a 这个位置,这个位置对应的就是 aload_0 指令,那么 这个指令的作用是干嘛的呢。

      在文档中检索找到此位置,这个汇编指令代表的是:把本地变量表中的第0项(只要不是静态方法就是 this)放到栈里,然后执行第二条指令 invokespecial
      在这里插入图片描述

    2. invokespecial:可以看到右面的 #1 可以点进去进行跟踪。在文档找到描述,如下图,可以看到对应的16进制是 0Xb7。加载到栈中,然后是第三条指令,也就是最后一条指令。format中下面两个indexbyte1和indexbyte2 是指令的两个参数
      在这里插入图片描述

    3. return 返回指令,对应 b1

    4. 构造函数<init>的 三个指令 体现在十六进制的 class文件中如下所示。
      在这里插入图片描述

v、附加属性 attributes

这里默认的就是java的类名称
在这里插入图片描述

c、小总结

上面主要讲解了class文件的十六进制由JVM解释后的内容,通过JVM文档进行对应解释。

除了常量池之外的,存的内容都是常量池中的地址。

由常量池存放除常量池之外的其他内容的引用地址。
在这里插入图片描述

三、循序渐进增加内容,查看class文件

1、实现 interface 接口

a、测试小程序

package com.mashibing.jvm.c1_bytecode;

import java.io.Serializable;

public class T0101_ByteCode_With_Interfaces implements Cloneable, Serializable {
}

b、编译后class文件

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.mashibing.jvm.c1_bytecode;

import java.io.Serializable;

public class T0101_ByteCode_With_Interfaces implements Cloneable, Serializable {
    public T0101_ByteCode_With_Interfaces() {
    }
}

c、jclasslib 查看

多了两个实现的接口,可以看到存在了常量池。
在这里插入图片描述
在这里插入图片描述

2、添加 函数

a、测试小程序

package com.mashibing.jvm.c1_bytecode;

public class T0102_ByteCode02 {
    void m() {
        int i=0;
        int j = i++;
    }
}

b、编译后class文件

package com.mashibing.jvm.c1_bytecode;

public class T0102_ByteCode02 {
    public T0102_ByteCode02() {
    }

    void m() {
        int i = 0;
        ++i;
    }
}

c、jclasslib 查看

可以看到 自定义方法和构造函数也都放到了常量池中。

  1. 增加了一个自定义方法。和默认的构造函数。
    在这里插入图片描述
  2. 自定义函数对应的指令。
    在这里插入图片描述

3、增加类属性

a、测试小程序

package com.mashibing.jvm.c1_bytecode;

public class T0103_ByteCode03 {
    int i = 0;
    String s = "Hello ByteCode!";
}

b、编译后的class文件

package com.mashibing.jvm.c1_bytecode;

public class T0103_ByteCode03 {
    int i = 0;
    String s = "Hello ByteCode!";

    public T0103_ByteCode03() {
    }
}

c、jclasslib 查看

类属性,即类成员变量,也存放在了常量池中。
在这里插入图片描述
在这里插入图片描述

4、增加带参构造函数和类属性

a、测试小程序

package com.mashibing.jvm.c1_bytecode;

public class T0104_ByteCode04 {
    int i = 0;
    String s = "Hello ByteCode!";

    public T0104_ByteCode04(int i, String s) {
        this.i = i;
        this.s = s;
    }
}

b、编译后的class文件

package com.mashibing.jvm.c1_bytecode;

public class T0104_ByteCode04 {
    int i = 0;
    String s = "Hello ByteCode!";

    public T0104_ByteCode04(int i, String s) {
        this.i = i;
        this.s = s;
    }
}

c、jclasslib 查看

在这里插入图片描述
在这里插入图片描述
红框中是函数参数类型和返回值类型。
在这里插入图片描述

5、增加构造函数、类方法、类属性

a、测试小程序

package com.mashibing.jvm.c1_bytecode;

public class T0104_ByteCode05 {
    int i = 0;
    String s = "Hello ByteCode!";

    public T0104_ByteCode05(int i, String s) {
        this.i = i;
        this.s = s;
    }

    public void m() {}
}

b、编译后的class文件

package com.mashibing.jvm.c1_bytecode;

public class T0104_ByteCode05 {
    int i = 0;
    String s = "Hello ByteCode!";

    public T0104_ByteCode05(int i, String s) {
        this.i = i;
        this.s = s;
    }

    public void m() {
    }
}

c、jclasslib 查看

在这里插入图片描述

在这里插入图片描述

四、如何找到 JVM class 格式 官网文档

1、直接进入网址即可

这是java 19版本的 JVM
https://docs.oracle.com/javase/specs/jvms/se19/html/index.html

2、从oracle 官网开始找

  1. 进入oracle 官网:https://www.oracle.com/,通过product 找到java 所在地进来。
    在这里插入图片描述
  2. 进入到了java页面,然后点击右上角的 download
    在这里插入图片描述
  3. 然后进入到了下载页面,也就是默认是最新的java版本,目前是19版本,如下所示
    在这里插入图片描述
  4. 拉到下面,找到 document download 中 下面的 read me,点进去
    在这里插入图片描述
  5. 然后找到如图的 jdk document 中的红框中的第一个,(这两个都有用)。
    在这里插入图片描述
  6. 这就是jdk 文档了,也可以从上面搜索任意一个版本的java版本。
    在这里插入图片描述
  7. 找到 下面的 specifications 中的 language and VM 这就是我们此行的目的地了
    在这里插入图片描述
  8. 这里就是各个版本的 jdk 文档了,同时还有每个版本新增的亮点,如下图所示,点击下面的 HTML。
    在这里插入图片描述
  9. 进入到文档(如下图所示),然后找到第四章(如下图所示),这就是 class 文件 格式的 内容了。
    在这里插入图片描述
    在这里插入图片描述

3、总结

从上面的最后一张截图所在的网址里,就可以学习 class 文件 格式的内容,也就是博文最开始总结的 xmind 的内容。
下图中就是class 文件 中 最重要的 常量池中的 tag 的标识。
在这里插入图片描述

相关文章
|
15天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
45 2
|
18天前
|
存储 缓存 安全
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见。本文介绍了使用 `File.createTempFile` 方法和自定义创建临时文件的两种方式,详细探讨了它们的使用场景和注意事项,包括数据缓存、文件上传下载和日志记录等。强调了清理临时文件、确保文件名唯一性和合理设置文件权限的重要性。
42 2
|
12天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
20天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
3天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
3天前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
24 1
|
11天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
11天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
10天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
16天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
43 9