HuKai's Blog HuKai's Blog
首页
  • Java核心技术

    • Java基础
    • Java并发编程
    • JVM
    • Java新特性
  • Spring生态

    • Spring5
    • SpringMVC
    • SpringBoot
  • 开源框架

    • MyBatis
  • 计算机网络
  • 操作系统
  • 数据结构与算法
  • 设计模式
  • SQL数据库

    • MySQL
    • Oracle
  • NoSQL数据库

    • Redis
    • MongoDB
  • 页面样式

    • HTML
    • CSS
  • JavaScript

    • JavaScript基础
    • ECMAScript6教程
    • TypeScript
  • 前端框架

    • Vue
    • Webpack
  • NIO
  • Netty
  • RabbitMQ
  • 技术文档

    • GitHub技巧
    • 博客搭建
    • 技术笔记
  • 优质文章

    • 小技巧
    • 解决方案
GitHub (opens new window)

HuKai

梦想成为全栈的保安
首页
  • Java核心技术

    • Java基础
    • Java并发编程
    • JVM
    • Java新特性
  • Spring生态

    • Spring5
    • SpringMVC
    • SpringBoot
  • 开源框架

    • MyBatis
  • 计算机网络
  • 操作系统
  • 数据结构与算法
  • 设计模式
  • SQL数据库

    • MySQL
    • Oracle
  • NoSQL数据库

    • Redis
    • MongoDB
  • 页面样式

    • HTML
    • CSS
  • JavaScript

    • JavaScript基础
    • ECMAScript6教程
    • TypeScript
  • 前端框架

    • Vue
    • Webpack
  • NIO
  • Netty
  • RabbitMQ
  • 技术文档

    • GitHub技巧
    • 博客搭建
    • 技术笔记
  • 优质文章

    • 小技巧
    • 解决方案
GitHub (opens new window)
  • Java基础

  • Java并发编程

  • JVM

    • 初识JVM
    • 类文件结构详解上篇
    • 类文件结构详解下篇
      • 前言
      • 属性表结构
      • Code属性
        • 属性详解
        • 实例解析
      • Exceptions属性
        • 属性详解
        • 实例解析
      • LineNumberTable属性
        • 属性详解
        • 实例分析
      • LocalVariableTable属性
        • 属性详解
        • 实例分析
      • SourceFile属性
      • ConstantValue属性
        • 属性详解
        • 实例分析
      • InnerClasses属性
      • Deprecated及Synthetic属性
    • 虚拟机类加载机制
    • JVM内存结构
    • JVM对象模型
    • GC基础
    • GC算法与收集器
    • GC日志详解
  • Java新特性

  • Spring5

  • SpringMVC

  • SpringBoot

  • MyBatis

  • Java
  • JVM
HuKai
2022-02-13
目录

类文件结构详解下篇

# 前言

在类文件结构详解上篇一文中,我们介绍了class文件的构成,整个class文件一共包含3部分共16个属性:

  • 3个描述文件属性的数据项:魔数和主次版本号
  • 11个描述类属性的数据项:类、字段、方法等信息
  • 2个描述代码属性的数据项:属性表,描述方法体内的具体内容

其中文件属性和类属性在上一篇中已经有过介绍,本文将主要介绍一下属性表。

后续的讲解我们会使用下面的类作为样例,附上代码:

package com.hukai.demo.bytecode;

import java.beans.Transient;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 属性测试
 *
 * @author hukai
 */
public class AttributeDemo {

    // 被final修饰并且直接赋值,数据类型为基本类型或字符串类型,生成ConstantValue属性进行初始化
    private final int anInt = 3;

    // 被final修饰并且直接赋值,数据类型不为基础类型,在实例构造器`init`方法里初始化
    private final Date anDate = new Date();

    // 在`clinit`方法(静态代码块)里初始化
    public static final int bnInt = 4;

    @Deprecated
    public static final Date bnDate = new Date();

    @Transient
    public String convertToString(String dateString) throws RuntimeException, ParseException {
        try {
            if (dateString == null) {
                throw new NullPointerException("字符串为空");
            }
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Date date = sdf.parse(dateString);
            return String.valueOf(date.getTime());
        } catch (NullPointerException | ArrayIndexOutOfBoundsException e){
            throw new RuntimeException("出现异常");
        }
    }

    class Sms {
        private String phone;

        public String getPhone() {
            return phone;
        }

        public void setPhone(String phone) {
            this.phone = phone;
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 属性表结构

属性表(attribute_info)在前面的讲解之中已经出现过数次,在Class文件、字段表、方法表都可以携带自己的属性表集合,以用于推述某些场景专有的信息。其结构如下图:

对于每个属性(attribute_info),它的名称需要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的结构则是完全自定义的,只需要通过一个u4的长度属性去说明属性值所占的位数即可。一个符合规则的属性表应该满足下表中所定义的结构:

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u1 info attribute_length

为了能正确解析Class文件,Java虚拟机规范中预定义了21项虛拟机实现应当能识别的属性,下文中将对其中一些属性中的关键常用的部分进行讲解。

属性名称 使用位置 含义
Code 方法表 Java代码编译成的字节码指令
ConstantValue 字段表 final关键字定义的常量值
Deprecated 类文件、字段表、方法表 被声明为deprecated的方法和字段
Exceptions 方法表 方法抛出的异常
InnerClasses 类文件 内部类列表
LineNumberTale Code属性 Java源码的行号与字节码指令的对应关系
LocalVariableTable Code属性 方法的局部变量描述
SourceFile 类文件 源文件名称
Synthetic 类文件、方法表、字段表 标识方法或字段是由编译器自动生成的

# Code属性

# 属性详解

Java程序方法体中的代码经过Javac编译器处理后,最终变为字节码指令存储在Code属性内。Code属性出现在方法表的属性集合之中,但并非所有的方法表都必须存在这个屬性,譬如接口或者抽象类中的方法就不存在Code属性,如果方法表有Code属性存在,那么它的结构如下所示:

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 max_stack 1
u2 max_locals 1
u4 code_length 1
u1 code code_length
u2 exception_table_length 1
exception_info exception_table exception_table_length
u2 attributes_count 1
attribute_info attributes attributes_count

下面对这些数据项逐一讲解:

  • attribute_name_index是一个指向常量池中某一个CONSTANT_Utf8_info常量的索引,取值固定为Code。代表了该属性的属性名称
  • attribute_length指示了属性值的长度,由于属性名称索引与属性长度一共为6字节,所以属性值的长度固定为整个属性表长度减去6个字节
  • max_stack表示操作数栈的最大深度,jvm运行时会根据这个值来分配栈帧中的操作数栈深度
  • max_locals表示局部变量表所需要的存储空间,单位为slot。并不是所有局部变量占用的slot之和,当一个局部变量的生命周期结束后,其所占用的slot将分配给其它依然存活的局部变量使用,按此方式计算出方法运行时局部变量表所需的存储空间
  • code_length和code用来存储Java源程序编译后生成的字节码指令。code_length代表字节码长度,code是用于存储字节码指令的一系列字节流。那么每个指令就是一个u1类型的单字节,当虚拟机读取到code中的一个字节码时,就可以对应找出这个宇节码代表的是什么指令.并且可以知道这条指令后面是否需要跟随参数,以及参数该如何理解
  • exception_table_length表示异常表占用的字节数
  • exception_table表示具体的异常表
  • Code属性本身还有自己的一些属性表,包括LineNumberTable、LocalVariableTable和StackMapTable,这些属性不是必须的,如果有的话,会在attributes_count和attributes中体现出来

tips:Slot,虚拟机为局部变量分配内存所使用的最小单位,长度不超过32位的数据类型占用1个Slot,64位的数据类型(long和double)占用2个Slot

# 实例解析

使用javap -v命令解析字节码内容,查看convertToString方法的解析结果如下:

stack=3, locals=4分别代表了max_stack=3和max_locals=4,即操作数栈的最大深度为3,局部变量表所需要的存储空间为4 slot。

arg_size代表参数个数。需要注意的是如果方法不是static修饰的,会自带一个this参数,此时arg_size=入参个数+1。虚拟机在解析class文件中的方法时,会判断参数数量args_size是否大于MAX_ARGS_SIZE,如果大于则就会报错了。MAX_ARGS_SIZE为255。

后续为编译出的字节码指令,在此不做详解。

最后为exception_table属性,很明显他对应着try catch这种东西,如果你的代码里没有try catch代码块,是不会生成exception_table的,它的结构如下:

类型 名称 数量
u2 start_pc 1
u2 end_pc 1
u2 handler_pc 1
u2 catch_type 1

start_pc和end_pc划分了try{},其对应字节码指令的行号,而catch_type代表了catch(exception)里面的那个参数exception,如果抓到异常就转到handler_pc处理。

# Exceptions属性

# 属性详解

这里的Exceptions属性是在方法表中与Code属性平级的一项属性,不是前面讲的异常表。Exceptions属性的作用是列举出方法中可能抛出的受査异常,也就是方法描述时在throws关键字后面列举的异常。结构如下:

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 number_of_exceptions 1
u2 exception_index_table number_of_exceptions
  • number_of_exceptions表示可能抛出number_of_exceptions种 受查异常
  • exception_index_table为异常索引集合,一组u2类型 exception_index的集合,每一个exception_index为一个指向常量池中一CONSTANT_Class_info型常量的索引,代表该受查异常的类型

# 实例解析

使用IDEA的jclasslib插件查看字节码信息,可以看到convertToString方法的Exceptions属性分析结果如下:

可以看到声明了RuntimeException和ParseException两个异常,皆为throws关键字后面列举的异常。

# LineNumberTable属性

# 属性详解

LineNumberTable属性用于描述Java源码行号与字节码行号之间的对应关系,它并不是运行时必需的属性,但默认会生成到Class文件之中,可以在Javac中分别使用-g:none或-g:lines选项来取消或要求生成这项信息。

如果选择不生成LineNumberTable属性,对程序运行产生的最主要的影响就是当抛出异常时,堆栈中将不会显示出错的行号,并且在调试程序的时候,也无法按照源码行来设置断点。属性结构如下表:

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 line_number_table_length 1
line_number_info line_number_table line_number_table_length

line_number_table是一个数量为line_number_table_length、类型为line_number_info的集合,line_number_info结构如下:

类型 名称 数量 含义
u2 start_pc 1 字节码偏移量
u2 line_number 1 java源文件行号

# 实例分析

使用jclasslib插件查看convertToString方法的Code属性下的LineNumberTable属性如下:

# LocalVariableTable属性

# 属性详解

LocalVariableTable属性用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系,它也不是运行时必需的属性,但默认会生成到Class文件之中,可以在Javac中分别使用-g:none或-g:vars选项来取消或要求生成这项信息。

如果没有生成这项属性,最大的影响就是当其他人引用这个方法时,所有的参数名称都将会丢失,IDE将会使用诸如arg0、argl之类的占位符代替原有的参数名,这对程序运行没有影响,但是会对代码编写带来较大不便,而且在调试期间无法根据参数名称从上下文中获得参数值。其结构如下表:

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 local_variable_table_length 1
local_variable_info local_variable_table local_variable_table_length

其中local_variable_info项目代表了一个栈帧与源码中的局部变量的关联,其结构如下:

类型 名称 数量 含义
u2 start_pc 1 变量生命周期开始时的字节码偏移量
u2 length 1 变量作用范围覆盖的字节数
u2 name_index 1 索引值,指向变量名称
u2 descriptor_index 1 索引值,指向变量描述符
u2 index 1 变量在栈帧中slot的位置

# 实例分析

查看convertToString方法的Code属性下的LocalVariableTable属性如下:

可以看到虽然我们方法体中没有用到this关键字,但是LocalVariableTable中还是有this这个局部变量,其作用范围是整个方法体。当然,静态方法是不会有this这个局部变量的。

# SourceFile属性

SourceFile属性用于记录生成这个class文件的源码文件名称。这个属性也是可选的,可以分别使用Javac的-g:none或-g:source选项来关闭或要求生成这项信息。

对大多数文件,类名和文件名是一致的,少数特殊类除外(如:内部类),如果不生成这项属性,抛出异常时,堆找中将不会显示出错代码所属的文件名。这是一个定长的属性,结构如下表:

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 sourcefile_index 1

sourcefile_index是指向常量池中一CONSTANT_Utf8_info类型常量的索引,常量的值为源码文件的文件名。实例如下:

# ConstantValue属性

# 属性详解

ConstantValue属性的作用是通知虚拟机自动为常量赋值。我个人对这个属性的理解是:当某个类变量被final修饰并且直接赋值,并且该变量的数据类型为基本类型或字符串类型,就生成ConstantValue属性进行初始化。如果变量不是基本类型或字符串类型,则在实例构造器init方法里初始化,若变量还被static修饰,则在静态初始化方法clinit里初始化。属性结构如下表:

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 constantvalue_index 1

constantvalue_index数据项代表了常量池中一个字面量常量的引用,它指向的应该是一个基本数据类型常量或者CONSTANT_String_info常量中的一种。

# 实例分析

AttributeDemo类中定义了四个字段,查看字节码分析结果如下:

可以看到只有两个字段(anInt、bnInt)生成了ConstantValue属性,它们的共同特点是都被final修饰且为基本类型。

# InnerClasses属性

InnerClasses属性用于记录内部类与宿主类之间的关联。如果一个类中定义了内部类,那编译器将会为它以及它所包含的内部类生成InnerClasses属性。结构如下表:

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 number_of_classes 1
inner_classes_info inner_classes number_of_classes

数据项number_of_classes代表需要记录多少个内部类信息,每一个内部类的信息都由一个inner_classes_info表进行描述。inner_classes_info表的结构如下表:

类型 名称 数量 含义
u2 inner_class_info_index 1 指向内部类CONSTANT_Class_info类型常量索引
u4 outer_class_info_index 1 指向宿主类CONSTANT_Class_info类型常量索引
u2 inner_name_index 1 索引值。指向内部类名称,如果为匿名内部类,则该值为0
u2 inner_name_access_flags 1 类似于access_flags,是内部类的访问标志

实例分析如下:

# Deprecated及Synthetic属性

Deprecated和Synthetic两个属性都属于标志类型的布尔属性,只存在有和没存的区别,没有属性值的概念。

Deprecated属性用于表示某个类、字段或者方法,已经被程序作者定为不再推荐使用,它可以通过在代码中使用@dcprecated注释进行设置。

Synthetic属性代表此字段或者方法并不是由Java源码直接产生的,而是由编译器自行添加的。在JDK1.5之后,标识一个类、字段或者方法是编译器自动产生的,也可以设置它们访问标志中的ACC_SYNTHETIC标志位。

Deprecated和Synthetic属性的结构非常简单,如下表:

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
编辑 (opens new window)
#JVM
上次更新: 2022/03/20, 11:17:00
类文件结构详解上篇
虚拟机类加载机制

← 类文件结构详解上篇 虚拟机类加载机制→

最近更新
01
MyBatisPlus
03-20
02
MyBatis源码剖析-延迟加载
03-20
03
MyBatis源码剖析-二级缓存
03-20
更多文章>
Theme by Vdoing | Copyright © 2021-2022 HuKai | 赣ICP备17016768号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式