Java虚拟机——类文件结构

1.概述

编译后的 .class 文件是虚拟机实现平台移植性的根本,下来我们一起来一步一步分析一下 .class 文件。
(ps:此文章借鉴了周志明老师写的《深入理解Java虚拟机》中的第六章,文中用的代码比较差,当时代码规范不是很好)

2.Class 类文件的结构

Class文件是一组以8位字节为基础单位的二进制流,各个数据严格按顺序排列在一起,中间没有分隔符。
虚拟机规定,Class 文件采用一种类似于C语言结构体的伪结构来存储数据,伪结构中只有两种数据类型:无符号数
无符号数:属于基本类型,以u1,u2,u4,u8分别代表1个字节,2个字节,4个字节,8个字节的无符号数,可以用来描述数字、索引引用、数量值或者按照 utf-8 编码构成字符串值。
:由无符号数或者其他表作为数据项构成的复合数据类型。

Class文件本质上就是一张表,由一下数据项构成:
在这里插入图片描述
分析一下class文件内容
在这里插入图片描述
代码是我电脑上随便一个 //一道简单的算法题

public class Bgap {
    public static void main(String[] args) {
        Solution solution = new Solution();
        System.out.println(solution.binaryGap(22));
    }

    static class Solution {
        public int binaryGap(int N) {
            String s = Integer.toBinaryString(N);
            System.out.println(s);
            int max = 0;
            String x = "1";
            char[] chars = s.toCharArray();
            for (int i = 0; i < s.length(); i++) {
                System.out.println(chars[i]);
                if (1 == 1) {
                    System.out.println(111);
                    int count = 0;
                    int k;
                    for (k = i + 1; k < s.length(); k++) {
                        if (chars[k] == 0) {
                            count++;
                        } else {
                            break;
                        }
                    }
                    if (count > max) {
                        max = count;
                    }
                    i = k;
                }
            }
            return max;
        }
    }
}

1.魔数与Class文件版本
  • 每个Class文件的前四个字节 0xCAFEBABE (咖啡宝贝,这也预示这Java图标的产生)称为魔数,唯一作用是用来在确认这个Class文件是否能被虚拟机所接受。

  • 第5、6字节代表次版本号,第7、8字节代表主版本号(0x0034为52,对应JDK版本1.8,jdk 1.1为45,每更新一次大版本版本号加一)

2.常量池

接着主版本号后面8、9位就是常量池的入口,是Class文件的资源文件,占用Class文件空间最大数据之一,也是文件中第一个出现的表类型数据项目。

由于常量池的数量不定,所以在入口处放置一项u2类型的数据,代表常量池容量计数值,需要注意的是这个常量值是从1开始计数的,空出0的意义是用0代表不引用任何常量池项目
在这里插入图片描述
0x002b代表十进制的 43则代表常量池中有42项常量。

常量池中主要存放两大类常量:字面量和符号引用字面量比较接近Java语言的常量概念,如文本字符串、声明为final的常量等。
而符号引用则属于编译原理方面的概念,它包括三方面的内容:

  • 类和接口的全限定名(Fully Qualified Name);
  • 字段的名称和描述符(Descriptor);
  • 方法的名称和描述符;

看看这个例子的第一项,容量计数后面的第一个字节标识这个常量的类型,是0x0a,即10,查表可知是类方法的符号引用,这个常量表的结构如下:
在这里插入图片描述
发现name_index是8(0x0008),descriptor_index是26(0x001a)。
Java已经为我们提供了一个自动解析常量池的工具javap我们可以通过javap -verbose class文件名

Classfile /D:/JavaProject/LeetCode/out/production/LeetCode/com/Bgap.class
  Last modified 2019-6-5; size 647 bytes
  MD5 checksum 09b027b1922fd39f05da720e9b142713
  Compiled from "Bgap.java"
public class com.Bgap
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #8.#26         // java/lang/Object."<init>":()V
   #2 = Class              #27            // com/Bgap$Solution
   #3 = Methodref          #2.#26         // com/Bgap$Solution."<init>":()V
   #4 = Fieldref           #28.#29        // java/lang/System.out:Ljava/io/PrintStream;
   #5 = Methodref          #2.#30         // com/Bgap$Solution.binaryGap:(I)I
   #6 = Methodref          #31.#32        // java/io/PrintStream.println:(I)V
   #7 = Class              #33            // com/Bgap
   #8 = Class              #34            // java/lang/Object
   #9 = Utf8               Solution
  #10 = Utf8               InnerClasses
  #11 = Utf8               <init>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               LocalVariableTable
  #16 = Utf8               this
  #17 = Utf8               Lcom/Bgap;
  #18 = Utf8               main
  #19 = Utf8               ([Ljava/lang/String;)V
  #20 = Utf8               args
  #21 = Utf8               [Ljava/lang/String;
  #22 = Utf8               solution
  #23 = Utf8               Lcom/Bgap$Solution;
  #24 = Utf8               SourceFile
  #25 = Utf8               Bgap.java
  #26 = NameAndType        #11:#12        // "<init>":()V
  #27 = Utf8               com/Bgap$Solution
  #28 = Class              #35            // java/lang/System
  #29 = NameAndType        #36:#37        // out:Ljava/io/PrintStream;
  #30 = NameAndType        #38:#39        // binaryGap:(I)I
  #31 = Class              #40            // java/io/PrintStream
  #32 = NameAndType        #41:#42        // println:(I)V
  #33 = Utf8               com/Bgap
  #34 = Utf8               java/lang/Object
  #35 = Utf8               java/lang/System
  #36 = Utf8               out
  #37 = Utf8               Ljava/io/PrintStream;
  #38 = Utf8               binaryGap
  #39 = Utf8               (I)I
  #40 = Utf8               java/io/PrintStream
  #41 = Utf8               println
  #42 = Utf8               (I)V
{
  public com.Bgap();
    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 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/Bgap;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: new           #2                  // class com/Bgap$Solution
         3: dup
         4: invokespecial #3                  // Method com/Bgap$Solution."<init>":()V
         7: astore_1
         8: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        11: aload_1
        12: bipush        22
        14: invokevirtual #5                  // Method com/Bgap$Solution.binaryGap:(I)I
        17: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
        20: return
      LineNumberTable:
        line 5: 0
        line 6: 8
        line 7: 20
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      21     0  args   [Ljava/lang/String;
            8      13     1 solution   Lcom/Bgap$Solution;
}
SourceFile: "Bgap.java"
InnerClasses:
     static #9= #2 of #7; //Solution=class com/Bgap$Solution of class com/Bgap
3.访问标志

访问标志(access_flags): 用于识别一些类或者接口层次的访问信息, 包括:这个Class是类还是接口; 是否定义为public类型;如果是类的话是否声明为final等. access_flags一共有16个标志位可以使用, 当前之定义了其中8个,没有使用到的标志位一律是 0
在这里插入图片描述

由于access_flags是两个字节大小,一共有十六个标志位可以使用,当前仅仅定义了8个,没有用到的标志位都是0。对于一个类来说,可能会有多个访问标志,这时就可以对照上表中的标志值取或运算的值。
在这里插入图片描述
拿上面那个例子来说,它的访问标志值是0x0021,查表可知,这是ACC_PUBLIC和ACC_SUPER值取或运算的结果。所以这个类的访问标志就是ACC_PUBLIC和ACC_SUPER

在这里插入图片描述

4.类索引,父类索引和接口索引集合

接下来就是类索引(this_class)和父类索引(super_class),这两个数据都是u2类型的,而接下来的接口索引集合是一个u2类型的集合,class文件由这三个数据项来确定类的继承关系。由于Java中是单继承,所以父类索引只有一个;但Java类可以实现多个接口,所以接口索引是一个集合。

类索引用来确定这个类的全限定名,这个全限定名就是说一个类的类名包含所有的包名,然后使用”/”代替”.”。比如Object的全限定名是java.lang.Object。父类索引确定这个类的父类的全限定名,除了Object之外,所有的类都有父类,所以除了Object之外所有类的父类索引都不为0.接口索引集合存储了implements语句后面按照从左到右的顺序的接口。

类索引和父类索引都是一个索引,这个索引指向常量池中的CONSTANT_Class_info类型的常量。然后再CONSTANT_Class_info常量中的索引就可以找到常量池中类型为CONSTANT_Utf8_info的常量,而这个常量保存着类的全限定名。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
this_class的值是0x0007,即十进制的7,我们根据cmd自动解析知道,指向的CONSTANT_Class_info中的索引是33,常量池中索引是33的CONSTANT_Utf8_info的常量值是com/Bgap。这样就解析到了这个类的全限定名,类的父类的全限定名也可以这样解析。

由于这个类没有实现接口,所以接口索引集合的容量计数是0。如果容量计数是0,就不需要存储接口的信息。

5.字段表集合

字段表(field_info)用于描述接口或者类中声明的变量.字段包括(field)包括类级变量以及实例级变量,但不包括在方法内部的局部变量. 每个字段可以包括:
字段的作用域(public,private,protected修饰符),是实例变量还是类变量(static修饰符),可变性(final),并发可见性(volatile修饰符,是否强制从主内存读写),可否被序列化(transient修饰符),字段数据类型(基本类型,对象,数组),字段名称, 类型和名称需要通过常量池中的常量来描述
在这里插入图片描述
其中的字段修饰符access_flags,和类中的access_flags类似,对于字段来说可以设置的标志位及含义如下:
在这里插入图片描述
access_flags给出了字段中所有可以用布尔值表示的修饰符,剩下的信息就是字段的名字、变量类型等信息。access_flags后面的是name_index和descriptor_index,前者是字段名的常量池索引,后者是字段描述符的常量池索引。name_index可以描述字段的名字,descriptor_index可以描述字段的数据类型。不过,对于方法的描述符来说就要复杂一些,因为一个方法除了返回值类型,还有参数类型,而且参数的个数还不确定。根据描述符规则,这些类型都使用一个大写字母来表示,如下表:
在这里插入图片描述
本实例中无字段所以是0x0000
在这里插入图片描述

6.方法表集合

在Class文件,字段表,方法表都可以携带自己的属性表集合,以用于某些场景专有的信息.例如Code属性,Java程序方法体中的代码经过Javac编译器处理后,最终变为字节码指令存储在Code属性内.Code属性出现在方法表的属性集合中
在这里插入图片描述

7.属性表集合

在Class文件,字段表,方法表都可以携带自己的属性表集合,以用于某些场景专有的信息.例如Code属性,Java程序方法体中的代码经过Javac编译器处理后,最终变为字节码指令存储在Code属性内.Code属性出现在方法表的属性集合中

此处还有11个属性呢,可以下去查看一下书籍

潦草结束了,到时候复习的时候估计会看晕

为什么要学JVM1、一切JAVA代码都运行在JVM之上,只有深入理解虚拟机才能写出更强大的代码,解决更深层次的问题。2、JVM是迈向高级工程师、架构师的必备技能,也是高薪、高职位的不二选择。3、同时,JVM又是各大软件公司笔试、面试的重中之重,据统计,头部的30家互利网公司,均将JVM作为笔试面试的内容之一。4、JVM内容庞大、并且复杂难学,通过视频学习是最快速的学习手段。课程介绍本课程包含11个大章节,总计102课时,无论是笔试、面试,还是日常工作,可以让您游刃有余。第1章 基础入门,从JVM是什么开始讲起,理解JDK、JRE、JVM的关系,java的编译流程和执行流程,让您轻松入门。第2章 字节码文件,深入剖析字节码文件的全部组成结构,以及javap和jbe可视化反解析工具的使用。第3章 类的加载、解释、编译,本章节带你深入理解类加载器的分类、范围、双亲委托策略,自己手写类加载器,理解字节码解释器、即时编译器、混合模式、热点代码检测、分层编译等核心知识。第4章 内存模型,本章节涵盖JVM内存模型的全部内容,程序计数器、虚拟机栈、本地方法栈、方法区、永久代、元空间等全部内容。第5章 对象模型,本章节带你深入理解对象的创建过程、内存分配的方法、让你不再稀里糊涂。第6章 GC基础,本章节是垃圾回收的入门章节,带你了解GC回收的标准是什么,什么是可达性分析、安全点、安全区,四种引用类型的使用和区别等等。第7章 GC算法与收集器,本章节是垃圾回收的重点,掌握各种垃圾回收算法,分代收集策略,7种垃圾回收器的原理和使用,垃圾回收器的组合及分代收集等。第8章 GC日志详解,各种垃圾回收器的日志都是不同的,怎么样读懂各种垃圾回收日志就是本章节的内容。第9章 性能监控与故障排除,本章节实战学习jcmd、jmx、jconsul、jvisualvm、JMC、jps、jstatd、jmap、jstack、jinfo、jprofile、jhat总计12种性能监控和故障排查工具的使用。第10章 阿里巴巴Arthas在线诊断工具,这是一个特别小惊喜,教您怎样使用当前最火热的arthas调优工具,在线诊断各种JVM问题。第11章 故障排除,本章会使用实际案例讲解单点故障、高并发和垃圾回收导致的CPU过高的问题,怎样排查和解决它们。课程资料课程附带配套项目源码2个159页高清PDF理论篇课件1份89页高清PDF实战篇课件1份Unsafe源码PDF课件1份class_stats字段说明PDF文件1份jcmd Thread.print解析说明文件1份JProfiler内存工具说明文件1份字节码可视化解析工具1份GC日志可视化工具1份命令行工具cmder 1份学习方法理论篇部分推荐每天学习2课时,可以在公交地铁上用手机进行学习。实战篇部分推荐对照视频,使用配套源码,一边练习一遍学习。课程内容较多,不要一次性学太多,而是要循序渐进,坚持学习。      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值