美文网首页
Java的class字节码结构

Java的class字节码结构

作者: 碎念枫子 | 来源:发表于2023-04-08 17:23 被阅读0次

情景

问:Java中的String的字符串长度有限吗?

答:我知道茴的四种写法,你看啊。。。。

问:。。。。

探索

为了了解这问题,我们需要区探究一下class文件

class文件又叫字节码文件,是它为java实现了跨平台运行的能力。

字节码也解除了虚拟机和java之间的耦合,因为java虚拟机可以支持其他语言上生成的字节码,例如JRuby,Groovy。

从纵观角度来看,class文件只有两种数据结构:无符号数

  • 无符号数:属于基本的数据类型,以u1,u2,u4,u8来分别代表1字节2字节4字节8字节的无符号数,他可以用来描述数字、索引引用、数值或者UTF-8字符串编码。
  • :表是由多个无符号数或者其他表结构组合的复合数据类型,class文件中的所有的表结构都以_info结尾。其实class文件就是一张表。

这些结构按照预先规定好的顺序紧密的相连,结构顺序如下所示:

当jvm加载某个class文件时,jvm就是根据上图的结构区解析class文件,并加载到内存中,并根据下图的情况分配内存空间。

用实例分析

首先编写一段简单的java代码

import java.io.Serializable;
public class Test implements Serializable, Cloneable{
      private int num = 1;
      public int add(int i) {
          int j = 10;
          num = num + i;
          return num;
     }
}

然后通过javac生成Test.class,然后用16进制编译器打开:

  • 魔数:开头的四位 ca fe ba be,他是固定值,用来判断是否标准class文件
  • 版本号00 00 00 34,前两位代表次版本号(minor_version),后两位代表主版本号(major_version)。说明当前的版本号是52.0对应jdk1.8.0
  • 常量池:它是一个叫做常量池的表(cp_info),在常量池中保存了各种类的信息,比如类的名称、父类、方法、参数类型等。

常量池中的每一个类项都是一个表,共有14种类型,如下:

常量池中的每一项都会有一个u1大小的tag值,用于标记当前数据结构属于哪一种表。

我们以CONSTANT_Class_infoCONSTANT_Utf8_info两张表举例说明:

table CONSTANT_Class_info {
    u1  tag = 7;
    u2  name_index;
}
  • tag:占用一个字节大小。比如值为 7,说明是 CONSTANT_Class_info 类型表。
  • name_index:是一个索引值,可以将它理解为一个指针,指向常量池中索引为 name_index 的常量表。比如 name_index = 2,则它指向常量池中第 2 个常量。

接下来我们来看看CONSTANT_Utf8_info的表结构:

table CONSTANT_utf8_info {
    u1  tag;
    u2  length;
    u1[] bytes;
}
  • tag:值为1 ,表示是CONSTANT_Utf8_info
  • length:表示u1[]数组的长度,例如length=5,则表示接下来5个连续的u1类型数据
  • bytes:u1 类型数组,长度为上面第 2 个参数 length 的值。

我们java代码声明的String字符串最终的存储格式就是CONSTANT_Utf8_info,因此length最大能表示的长度就是u2能代表的最大值65536个,但是需要额外的两个字节来保存null值,因此String所能表示的最大长度是65536-2=65534

不难看出常量池内部的表中也有相互之间的引用,用一张图来表示CONSTANT_Class_infoCONSTANT_Utf8_info表格间的关系

理解了常量池内部的数据结构之后,我们看一下实例代码解析过程。从版本号之后开始解析:

  • 00 1d:说明常量计数器的值为29,由于下标为0的常量被JVM使用,我们实际上常量池的大小为28.

  • ** 0a**:可以查到表类型为CONSTANT_Methodref_info,因此常量池中的第一个常量类型为方法引用表。其结构如下:

CONSTANT_Methodref_info {
    u1 tag = 10;
    u2 class_index;        指向此方法的所属类
    u2 name_type_index;    指向此方法的名称和类型

}
  • 00 06:指向常量池中的第6个常量
  • 00 15:指向常量池中的第21个常量

第一个表已经解读完了,接下来时第二个表

  • 09:当前时字段引用表,结构如下
CONSTANT_Fieldref_info{
    u1 tag;
    u2 class_index;        指向此字段的所属类
    u2 name_type_index;    指向此字段的名称和类型
}
  • 00 05 :指向常量池中第 5 个常量。
  • 00 16:指向常量池中第 22 个常量。

我们已经解析了常量池中的两个常量, 后面的常量解析方法如出一辙,实际上我们可以借助javap命令来查看常量池中的内容

javap -v Test.class

其结果正如我们前面解析的一样,其中下标为21的常量类型为NameAndType,它的数据结构是

CONSTANT_NameAndType_info{
    u1 tag;
    u2 name_index;    指向某字段或方法的名称字符串
    u2 type_index;    指向某字段或方法的类型字符串

}
  • 而下标在 21 的 NameAndTypename_indextype_index 分别指向了 13 和 14,也就是“<init>”“()V”。因此最终解析下来常量池中第 1 个常量的解析过程以及最终值如下图所示:

经过仔细分析,我们可以知道常量池中第一个常量保存的是Object中的默认构造方法。

  • 常量池之后,是访问标志00 21,它占两个字节,前面的表中有白框标识:它代表了类或者方法的修辞方式,含义如下图所示。
  • 00 05:类索引
  • 00 06: 父类索引
  • 00 02 :接口索引计数器

回顾常量池

类名是Test,父类是Object,我们接着看接口计数器为2 说明下面的4个字节描述了两个接口

  • 00 07:常量值为"Serializable"
  • 00 08 :常量值为"Cloneable"

综上:当前类为 Test 继承自 Object 类,并实现了“Serializable”和“Cloneable”这两个接口。

接下里是字段表

  • 00 02 :字段计数器:表示类中声明类两个字段,接着回出现两个字段表的数据结构,字段表数据结构如下:
CONSTANT_Fieldref_info{
    u2  access_flags    字段的访问标志
    u2  name_index          字段的名称索引(也就是变量名)
    u2  descriptor_index    字段的描述索引(也就是变量的类型)
    u2  attributes_count    属性计数器
    attribute_info
}
  • 00 02:字段访问标志,代表是private类型,解析如下图所示:
  • 00 09:变量名索引,变量名是num
  • 00 0a:变量类型,类型是I,说明是int类型变量

接下来的解析如出一辙,我们说一下注意事项。

  1. 字段表集合中不会列出从父类或者父接口中继承而来的字段。

  2. 内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。

方法表

方法表紧随字段表其后,也是从一个计数器开始

方法表的结构如下:

CONSTANT_Methodref_info{
    u2  access_flags;        方法的访问标志
    u2  name_index;          指向方法名的索引
    u2  descriptor_index;    指向方法类型的索引
    u2  attributes_count;    方法属性计数器
    attribute_info attributes;
}

访问标志的值如下:

第一个方法是构造方法,我们主要分析一下add方法:

从图中我们可以看出 add 方法的以下字段的具体值:

  1. access_flags = 00 01 也就是访问权限为 public。
  2. name_index = 00 11 指向常量池中的第 17 个常量,也就是“add”。
  3. type_index = 00 12 指向常量池中的第 18 个常量,也即是 (I)。这个方法接收 int 类型参数,并返回 int 类型参数。

属性表:在之前解析字段和方法的时候,在它们的具体结构中我们都能看到有一个叫作 attributes_info 的表,这就是属性表。

属性表并没有一个固定的结构,各种不同的属性只要满足以下结构即可:

CONSTANT_Attribute_info{
    u2 name_index;
    u2 attribute_length length;
    u1[] info;
}

我们接着往下看:

  • 00 01:属性计数器,说明只有一个属性
  • 00 0f:属性索引,通过查看常量,我们可以看出它是code属性表
code属性表中,最主要的就是一系列的字节码。通过`javap -v Test.class`我们可以查看到方法的字节码

JVM执行add方法时,就是通过这一系列的指令操作来完成的。

解答

String能保存的最大长度需要从两个角度来回答,在编译期还是运行期。

编译期由于是CONSTANT_Utf8_info格式存储的,所以最大长度是65534字节,这里需要注意,英文和数字是占用1字节,而汉字是占用两个字节,所以不一定能存到最大长度的字符。

运行期时字符串的内部是有char数组的value来存储的,数组的长度表示类型是int类型,所以这时候String的最大长度是Integer.MAX_VALUE (2147483647)了,大约运行时需要4BG内存才能达到最大最大字符串长度。

相关文章

  • 11_JVM学习笔记_字节码剖析

    字节码剖析 示例代码: 反编译信息如下: 字节码文件16进制 Java字节码结构 Class字节码中有两种数据类型...

  • JVM

    JVM 基础-类字节码详解 多语言编译为字节码在 JVM 运行 Java 字节码文件-- Class文件的结构属性...

  • 新鲜出炉,深入讲解java反射的底层原理,这篇算讲的不错了!

    反射 反射 Java代码和Java文件 Java文件和.class字节码文件 class字节码文件在内存中的位置 ...

  • 03 java字节码文件

    java源码经过编译,生成class字节码文件,JVM加载class文件执行。字节码文件将java语言与JVM解耦...

  • Java字节码结构解析

    本文通过解析Class文件中字节码的结构,来加深对Java类文件结构的理解。建议先阅读Java类文件结构解析这篇文...

  • JVM_字节码:字节码相关概述

    Java字节码的整体结构: Class字节码中有两种数据类型: 字节码数据直接量:这是基本的数据类型,共细分为u1...

  • Javassist 指南1

    1、读写字节码 Javassist 是一个能处理 Java字节码 的类库,Java字节码存储在class文件中,每...

  • Tomcat 类加载机制

    Java类(.java)—> 字节码⽂件(.class) —> 字节码⽂件需要被加载到jvm内存当中(这个过程就是...

  • Effect JAVA -机制与原理

    JAVA字节码.Class解析 不论该字节码文件来自何方,由哪种编译器编译,甚至是手写字节码文件,只要符合java...

  • android 虚拟机

    java:.java文件编译成.class文件的字节码,最终将字节码提供给jvm翻译成机器码。 android:....

网友评论

      本文标题:Java的class字节码结构

      本文链接:https://www.haomeiwen.com/subject/arkvddtx.html