Java的class字节码结构

2023-04-08  本文已影响0人  碎念枫子

情景

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

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

问:。。。。

探索

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

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

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

从纵观角度来看,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进制编译器打开:

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

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

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

table CONSTANT_Class_info {
    u1  tag = 7;
    u2  name_index;
}

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

table CONSTANT_utf8_info {
    u1  tag;
    u2  length;
    u1[] bytes;
}

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

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

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

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

}

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

CONSTANT_Fieldref_info{
    u1 tag;
    u2 class_index;        指向此字段的所属类
    u2 name_type_index;    指向此字段的名称和类型
}

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

javap -v Test.class

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

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

}

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

回顾常量池

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

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

接下里是字段表

CONSTANT_Fieldref_info{
    u2  access_flags    字段的访问标志
    u2  name_index          字段的名称索引(也就是变量名)
    u2  descriptor_index    字段的描述索引(也就是变量的类型)
    u2  attributes_count    属性计数器
    attribute_info
}

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

  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;
}

我们接着往下看:

code属性表中,最主要的就是一系列的字节码。通过`javap -v Test.class`我们可以查看到方法的字节码

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

解答

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

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

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

上一篇 下一篇

猜你喜欢

热点阅读