今天一起带大家深入理解Java中class文件内容,以及刨析字节码内容,一起跟着博主来学习吧,内容有点多点击关注,防止本文章丢失喔~~~
首先,先来看看java中jvm的结构图,他们是如何工作的!
Java文件结构
Javap命令解析
javap -c 对类文件进行反编译
javap -p 显示private方法和字段(Javap 会显示访问权限为public、protected、默认级别方法)
javap -v 显示更多详细信息,比如版本号、类访问权限、常量池信息
javap -l 显示行号表和局部变量表
javap -g 生成所有调试信息
java字节码官网地址: https://docs.oracle.com/javase/specs/jvms/se20/html/jvms-4.html#jvms-4.4
Class文件结构
ClassFile{
u4 magic; # 魔数
u2 minor_versoin; # 次版本号
u2 major_version; # 主版本号
u2 constant_pool_count; # constant_pool_cout(常量个数)
cp_info constant_pool[constant_pool_count-1]; # constant_pool(常量池表)
u2 access_flags; # 访问修饰符
u2 this_class; # This class Name
u2 super_class; # super class name
u2 interfaces_count; #Interfaces_count(接口数)
u2 interfaces[interfaces_count]; #interfaces(接口名称)
u2 fields_count; #fields_count
field_info fields[fields_count]; #fileds(字段表)
u2 methods_count; #methods_count(方法个数)
method_info mathods[methods_count]; #方法表
u2 attributes_count; #attruibute_count(附加属性个数)
attributes_info attributes[attributes_count]; #attrubites(附加属性表)
}
魔数
魔数: 文件的开头的 四个字节 是固定 值位 0xCAFEBABE
版本
Java版本与Major Version 关系
| JDK | major version |
|---|---|
| Java 1.1 | 45 |
| Java 1.2 | 46 |
| Java 1.3 | 47 |
| Java 1.4 | 48 |
| Java 5(1.5) | 49 |
| Java 6(1.6) | 50 |
| Java 7(1.7) | 51 |
| Java 8 | 52 |
| Java 9 | 53 |
| Java 10 | 54 |
| Java 11 | 55 |
常量池
1、常量池结构
constant_pool_count
struct{
u2 constant_pool_count; # constant_pool_cout(常量个数)
cp_info constant_pool[constant_pool_count-1]; # constant_pool(常量池表)
}
struct cp_info{
u1 tag;
u1 info[];
}
1.1、常量池描述:
我们的常量池可以看作我们的java class类的一个资源仓库(比如Java类定的方法和变量信息),我们后面的方法 类的信息的描述信息都是通过索引去常量池中获取。
注:常量池中的第0个位置被我们的jvm占用了表示为null 所以我们通过编译出来的常量池索引是从1开始的。【所指的个数是这个变量constant_pool_count】
1.2、常量池分类:
1.3、常量池细化
类访问标记
1、类访问标记权限
当前类名
1、描述:
this class name 占用二个字节,指向的常量池具体的常量
当前类的父类
1、描述:
this class name 占用二个字节,指向的常量池具体的常量
接口信息
1、描述:
当前类的接口信息(是否实现接口),java中支持最大实现65535个接口,在动态代理中Proxy类中限制了
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
字段表
1、字段表结构
struct {
u2 fields_count; #fields_count
field_info fields[fields_count]; #fileds(字段表)
}
field_info{
u2 access_flags; #权限修饰符
u2 name_index; #字段名称索引,指向常量池的字符串常量
u2 descciptor_index; #字段描述索引,指向常量池的字符串常量
u2 attribute_count; #属性表个数
attribute_info attributes[attribute_count];
}
2、access_flags字段访问标记
3、descciptor_index字段描述符
在JVM规范中,每个字段或者变量都有描述信息,描述信息的主要作用是 数据类型,方法参数列表,返回值类型等;
其中基本参数类型和void类型都是用一个大写的字符来表示,对象类型是通过一个大写L加全类名表示,这么做的好处就是在保证jvm能读懂class文件的情况下尽量的压缩class文件体积. 如下:
基本数据类型:
B---->byte
C---->char
D---->double
F----->float
I------>int
J------>long
S------>short
Z------>boolean
V------->void
对象类型:
String------>Ljava/lang/String;(后面有一个分号)
对于数组类型: 每一个唯独都是用一个前置 [ 来表示
比如: int[] ------>[ I,
int ------> (I)I
String [][]------>[[Ljava.lang.String;
用描述符来描述方法的,先参数列表,后返回值的格式,参数列表按照严格的顺序放在()中
比如源码 String getUserInfoByIdAndName(int id,String name) 的方法描述符号(
I,Ljava/lang/String;)Ljava/lang/String;
方法表
1、方法表的结构
struct{
u2 methods_count;
method_info methods[methods_count];
}
method_info{
u2 access_flags; # 权限修饰符
u2 name_index; # 字段名称索引
u2 desriptor_index; # 字段描述索引
u2 attributes_count; # 属性表个数
attribute_info attributes[attributes_count];
}
2、attribute_info方法表中的属性表
attribute_info{
u2 attribute_naem_index; # 指向常量池中的索引
u4 attribute_length;
u1 info[attribute_length];
}
3、code_atrribute属性表结构
Code属性是类文件中最重要的组成部分,包括方法的字节码,每个method都有且只有一个code属性
Code_attribute{
u2 attribute_name_index; 2个字节属性名称索引
u4 attribute_length; 4个字节属性所包含的字节数,注:不包括
attribute_name_index和attribute_length
u2 max_stack; 2个字节,最大操作数栈的深度
u2 max_locals; 2个字节, 最大局部变量表
u4 code_length; 该方法的字节码以及指令码
u1 code[code_length]; 具体指令
u2 exception_table_length; 异常表个数
{
u2 start_pc;
u2 end_pc; 抛出的异常由这个表处理
u2 handler_pc; 表示异常代码的开始处
u2 catch_type; 表示被处理流程的异常类型
} exception_table[exception_table_length]; 异常表结构,用于存放异常信息
u2 attribute_count;
attribute_info attributes[attribute_count];
}
LineNumberTable{
u2 attribute_name_index
u4 attribute_length
u2 line_number_table_length
line_number_info [line_number_table]
}
line_number_info{
u2 字节码对应关系
u2 start_pc
u2 源码中的行号
}
LocalVariableTable{
u2 attribute_name_index
u4 attribute_length
u2 local_variable_table_length
local_variable_info [local_variable_table]
}
local_variable_info{
u2 start_pc
u2 length
u2 name_index
u2 descriptor_index
u2 index
}
字节码刨析案例:
为了更加方便理解,在idea中安装插件jclasslib
1、java源代码
package com.yogurt.classParse;
/**
* @author yogurt
* @Date 2023/4/22 - 23:03 - 2023
*/
public class AddCode {
public int add(int a,int b){
return a+b;
}
public static void main(String[] args) {
int add = new AddCode().add(4, 6);
System.out.println("add = " + add);
}
}
2、反编译代码
Classfile /Users/yogurt/idea/class-parse/target/classes/com/yogurt/classParse/AddCode.class
Last modified 2023-7-22; size 915 bytes
MD5 checksum de4059f5ad74afcc415ef5b31730abf5
Compiled from "AddCode.java"
public class com.yogurt.classParse.AddCode
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #13.#32 // java/lang/Object."<init>":()V
#2 = Class #33 // com/yogurt/classParse/AddCode
#3 = Methodref #2.#32 // com/yogurt/classParse/AddCode."<init>":()V
#4 = Methodref #2.#34 // com/yogurt/classParse/AddCode.add:(II)I
#5 = Fieldref #35.#36 // java/lang/System.out:Ljava/io/PrintStream;
#6 = Class #37 // java/lang/StringBuilder
#7 = Methodref #6.#32 // java/lang/StringBuilder."<init>":()V
#8 = String #38 // add =
#9 = Methodref #6.#39 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#10 = Methodref #6.#40 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
#11 = Methodref #6.#41 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#12 = Methodref #42.#43 // java/io/PrintStream.println:(Ljava/lang/String;)V
#13 = Class #44 // java/lang/Object
#14 = Utf8 <init>
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 LocalVariableTable
#19 = Utf8 this
#20 = Utf8 Lcom/yogurt/classParse/AddCode;
#21 = Utf8 add
#22 = Utf8 (II)I
#23 = Utf8 a
#24 = Utf8 I
#25 = Utf8 b
#26 = Utf8 main
#27 = Utf8 ([Ljava/lang/String;)V
#28 = Utf8 args
#29 = Utf8 [Ljava/lang/String;
#30 = Utf8 SourceFile
#31 = Utf8 AddCode.java
#32 = NameAndType #14:#15 // "<init>":()V
#33 = Utf8 com/yogurt/classParse/AddCode
#34 = NameAndType #21:#22 // add:(II)I
#35 = Class #45 // java/lang/System
#36 = NameAndType #46:#47 // out:Ljava/io/PrintStream;
#37 = Utf8 java/lang/StringBuilder
#38 = Utf8 add =
#39 = NameAndType #48:#49 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#40 = NameAndType #48:#50 // append:(I)Ljava/lang/StringBuilder;
#41 = NameAndType #51:#52 // toString:()Ljava/lang/String;
#42 = Class #53 // java/io/PrintStream
#43 = NameAndType #54:#55 // println:(Ljava/lang/String;)V
#44 = Utf8 java/lang/Object
#45 = Utf8 java/lang/System
#46 = Utf8 out
#47 = Utf8 Ljava/io/PrintStream;
#48 = Utf8 append
#49 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#50 = Utf8 (I)Ljava/lang/StringBuilder;
#51 = Utf8 toString
#52 = Utf8 ()Ljava/lang/String;
#53 = Utf8 java/io/PrintStream
#54 = Utf8 println
#55 = Utf8 (Ljava/lang/String;)V
{
public com.yogurt.classParse.AddCode();
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 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/yogurt/classParse/AddCode;
public int add(int, int);
descriptor: (II)I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
LineNumberTable:
line 10: 0
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this Lcom/yogurt/classParse/AddCode;
0 4 1 a I
0 4 2 b I
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/yogurt/classParse/AddCode
3: dup
4: invokespecial #3 // Method "<init>":()V
7: iconst_4
8: bipush 6
10: invokevirtual #4 // Method add:(II)I
13: istore_1
14: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
17: new #6 // class java/lang/StringBuilder
20: dup
21: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
24: ldc #8 // String add =
26: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
29: iload_1
30: invokevirtual #10 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
33: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
36: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
39: return
LineNumberTable:
line 14: 0
line 15: 14
line 16: 39
LocalVariableTable:
Start Length Slot Name Signature
0 40 0 args [Ljava/lang/String;
14 26 1 add I
}
SourceFile: "AddCode.java"
3、class文件内容
4、class文件-魔数
代表class文件
| CA | FE | BA | BE |
|---|
现在我把CA FE BA BE修改成CA FE BA FF,如图:
此时在运行时,出现了无法兼容的一个错误: Incompatible magic value 3405691647 in class file com/yogurt/classParse/AddCode
5、class文件-版本号
对应java中jdk的版本, 这里16进制的34转成10进制为52,正好对应前面所说的java8版本
| 00 | 00 | 00 | 00 | 34 |
|---|
此时我将34修改成:99(没有对应的jdk版本)
在运行时出现了报错: 大概的意思意思就是只能识别52.0版本的,就是我们的jdk1.8
Exception in thread "main" java.lang.UnsupportedClassVersionError: com/yogurt/classParse/AddCode has been compiled by a more recent version of the Java Runtime (class file version 153.0), this version of the Java Runtime only recognizes class file versions up to 52.0
6、class文件-常量个数
这里16进制的38转成10进制为56, 前面说过常量是从下标0开始计算的, 我们可以看到反编译出来的是55个,其中加上下标0(jvm默认下标0的为null) ,刚刚好
| 00 | 38 |
|---|
Constant pool:
#1 = Methodref #13.#32 // java/lang/Object."<init>":()V
#2 = Class #33 // com/yogurt/classParse/AddCode
#3 = Methodref #2.#32 // com/yogurt/classParse/AddCode."<init>":()V
#4 = Methodref #2.#34 // com/yogurt/classParse/AddCode.add:(II)I
#5 = Fieldref #35.#36 // java/lang/System.out:Ljava/io/PrintStream;
#6 = Class #37 // java/lang/StringBuilder
#7 = Methodref #6.#32 // java/lang/StringBuilder."<init>":()V
#8 = String #38 // add =
#9 = Methodref #6.#39 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#10 = Methodref #6.#40 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
#11 = Methodref #6.#41 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#12 = Methodref #42.#43 // java/io/PrintStream.println:(Ljava/lang/String;)V
#13 = Class #44 // java/lang/Object
#14 = Utf8 <init>
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 LocalVariableTable
#19 = Utf8 this
#20 = Utf8 Lcom/yogurt/classParse/AddCode;
#21 = Utf8 add
#22 = Utf8 (II)I
#23 = Utf8 a
#24 = Utf8 I
#25 = Utf8 b
#26 = Utf8 main
#27 = Utf8 ([Ljava/lang/String;)V
#28 = Utf8 args
#29 = Utf8 [Ljava/lang/String;
#30 = Utf8 SourceFile
#31 = Utf8 AddCode.java
#32 = NameAndType #14:#15 // "<init>":()V
#33 = Utf8 com/yogurt/classParse/AddCode
#34 = NameAndType #21:#22 // add:(II)I
#35 = Class #45 // java/lang/System
#36 = NameAndType #46:#47 // out:Ljava/io/PrintStream;
#37 = Utf8 java/lang/StringBuilder
#38 = Utf8 add =
#39 = NameAndType #48:#49 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#40 = NameAndType #48:#50 // append:(I)Ljava/lang/StringBuilder;
#41 = NameAndType #51:#52 // toString:()Ljava/lang/String;
#42 = Class #53 // java/io/PrintStream
#43 = NameAndType #54:#55 // println:(Ljava/lang/String;)V
#44 = Utf8 java/lang/Object
#45 = Utf8 java/lang/System
#46 = Utf8 out
#47 = Utf8 Ljava/io/PrintStream;
#48 = Utf8 append
#49 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#50 = Utf8 (I)Ljava/lang/StringBuilder;
#51 = Utf8 toString
#52 = Utf8 ()Ljava/lang/String;
#53 = Utf8 java/io/PrintStream
#54 = Utf8 println
#55 = Utf8 (Ljava/lang/String;)V
使用插件也可以看出总共是56个常量,如下图:
7、class文件-常量
结合下面这张图分析
1、第1个常量分析
下面这个是第一个常量具体内容
#1 = Methodref #13.#32 // java/lang/Object."<init>":()V
结合上面的图可以看到Methodref常量的具体结构体为:
具体字节码内容, 其中A刚好对应tag中的10, class_info占两个字节: 00 0D (10进制内容为: 13) 正好对应下标为#13(#13 = Class )的内容, name_and_type_index占用两个字节: 00 20 (10进制内容为: 32) 正好对应下标为#32(#32 = NameAndType )的内容
| 0A | 00 | 0D | 00 | 20 |
|---|
2、第2个常量分析
下面是第二个常量的具体内容
#2 = Class #33 // com/yogurt/classParse/AddCode
结合上面的图可以看到Class常量的具体结构为:
具体字节码内容中, 07正好对应常量中的tag, name_index占用两个字节: 00 21 (10进制内容为: 33) 正好对应下标为#33( #33 = Utf8 )
| 07 | 00 | 21 |
|---|
3、第3个常量分析
下面是第三个常量的具体内容:
#3 = Methodref #2.#32 // com/yogurt/classParse/AddCode."<init>":()V
结合上面的图可以看到Methodref常量的具体结构体为:
具体字节码内容, 其中A刚好对应tag中的10, class_info占两个字节: 00 02 (10进制内容为: 2) 正好对应下标为#2(#2 = Class )的内容, name_and_type_index占用两个字节: 00 20 (10进制内容为: 32) 正好对应下标为#32(#32 = NameAndType )的内容
| 0A | 00 | 02 | 00 | 20 |
|---|
4、第最后个常量分析
这里因为时间和内容过多原因跳过其他50多个常量分析,具体可以按照上面提供的内容和步骤去分析, 直接分析最后一个常量内容:
#55 = Utf8 (Ljava/lang/String;)V
结合上面的图可以看到Utf-8常量的具体结构体为:
具体字节码内容, 其中A刚好对应tag中的1, length占两个字节: 00 15 (10进制内容为: 21) 对应bytes数组的长度为21, bytes占用1个字节具体内容为:
28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56
| 01 | 00 | 15 | 28 | 4C | 6A | 61 | 76 | 61 | 2F |
|---|---|---|---|---|---|---|---|---|---|
| 6C | 61 | 6E | 67 | 2F | 53 | 74 | 72 | 69 | 6E |
| 67 | 3B | 29 | 56 | **** | **** | **** | **** | **** | **** |
8、class文件-类访问修饰符
结合前面的内容可以看到修饰符占用两个字节:
| 00 | 21 |
|---|
结合下面的图可以看到:
value值中: 0x0001 + 0x0020 正好等于 21 ,所以当前类的权限修饰符是ACC_PUBLIC、ACC_SUPER
下面是反编译后的内容,正好和我分析的一致
Classfile /Users/yogurt/idea/class-parse/target/classes/com/yogurt/classParse/AddCode.class
Last modified 2023-7-22; size 915 bytes
MD5 checksum de4059f5ad74afcc415ef5b31730abf5
Compiled from "AddCode.java"
public class com.yogurt.classParse.AddCode
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
9、class文件-当前类名
当前类名占用两个字节, 对应class文件中的:
| 00 | 02 |
|---|
前面说过当前类的内容指向的是常量池中的索引, 对应常量池#2( #2 = Class #33 // com/yogurt/classParse/AddCode) , 正是我们的类AddCode
10、class文件-当前类的父类
当前类名占用两个字节, 对应class文件中的:
| 00 | 0D |
|---|
同样也是指向常量池中的内容, D的10进制为13,对应下标为#13(#13 = Class #44 // java/lang/Object)的内容, 是一个Object对象,根据所有类的父类是Object的特性,可以知道这个是没问题的,当前类没有使用extend继承任何类
11、class文件-接口信息
根据前面提供的接口信息可知, 接口内容分为接口数量、接口名称,其中接口数量占用两个字节, 接口名称占用两个字节
u2 interfaces_count; #Interfaces_count(接口数)
u2 interfaces[interfaces_count]; #interfaces(接口名称)
对应class文件字节码内容:
| 00 | 00 |
|---|
当前类没有实现任何接口所以字节码内容都是空
12、class文件-字段表
根据前面提供的字段表信息可知, 内容分为字段表数量、具体字段内容,其中字段数量占用两个字节, 具体字段内容是一个数组
struct {
u2 fields_count; #fields_count
field_info fields[fields_count]; #fileds(字段表)
}
field_info{
u2 access_flags; #权限修饰符
u2 name_index; #字段名称索引,指向常量池的字符串常量
u2 descciptor_index; #字段描述索引,指向常量池的字符串常量
u2 attribute_count; #属性表个数
attribute_info attributes[attribute_count];
}
对应class文件字节码内容:
| 00 | 00 |
|---|
当前类中没有具体的字段,所以为空
13、class文件-方法表
根据前面提供的方法表信息可知, 内容分为方法表数量、具体方法内容,其中方法数量占用两个字节, 具体方法内容是一个数组
struct{
u2 methods_count;
method_info methods[methods_count];
}
method_info{
u2 access_flags; # 权限修饰符
u2 name_index; # 字段名称索引
u2 desriptor_index; # 字段描述索引
u2 attributes_count; # 属性表个数
attribute_info attributes[attributes_count];
}
attribute_info{
u2 attribute_naem_index; # 指向常量池中的索引
u4 attribute_length;
u1 info[attribute_length];
}
Code_attribute{
u2 attribute_name_index; 2个字节属性名称索引
u4 attribute_length; 4个字节属性所包含的字节数,注:不包括
attribute_name_index和attribute_length
u2 max_stack; 2个字节,最大操作数栈的深度
u2 max_locals; 2个字节, 最大局部变量表
u4 code_length; 该方法的字节码以及指令码
u1 code[code_length]; 具体指令
u2 exception_table_length; 异常表个数
{
u2 start_pc;
u2 end_pc; 抛出的异常由这个表处理
u2 handler_pc; 表示异常代码的开始处
u2 catch_type; 表示被处理流程的异常类型
} exception_table[exception_table_length]; 异常表结构,用于存放异常信息
u2 attribute_count;
attribute_info attributes[attribute_count];
}
方法表数量对应字节码内容:
| 00 | 03 |
|---|
说明当前类存在三个方法,使用插件查看具体的内容如下:
可以看到确实是有三个方法,分别是init 、add、main
1、第一个方法:
第一个方法method_info具体内容对应的字节码:
| 00 | 01 | 00 | 0E | 00 | 0F | 00 | 01 |
|---|
1.1、修饰符:
修饰符占用两个字节对应具体字节码内容如下:
| 00 | 01 |
|---|
根据前面的修饰符的内容可以,当前方法是ACC_PUBLIC,使用插件查看具体内容,确实是public类型的:
1.2、方法名称索引
方法名称索引占用两个字节对应具体字节码内容如下:
| 00 | 0E |
|---|
方法名称索引指向常量池中的#14( #14 = Utf8 )位置,是一个init构造方法
1.3、方法描述索引
方法描述索引占用两个字节对应具体字节码内容如下:
| 00 | 0F |
|---|
方法描述索引指向常量池中的#15( #15 = Utf8 ()V)位置,是一个无参返回值
1.4、属性表个数
属性表个数占用两个字节对应具体字节码内容如下:
| 00 | 01 |
|---|
当前方法属性只有一个
1.5、方法属性
下面是方法属性的结构
attribute_info{
u2 attribute_naem_index; # 指向常量池中的索引
u4 attribute_length;
u1 info[attribute_length];
}
方法属性对应具体字节码内容如下:
| 00 | 10 | 00 | 00 | 00 | 2F |
|---|
1.5.1、方法属性名称
方法属性名称占用二个字节对应具体字节码内容如下:
| 00 | 10 |
|---|
方法属性名称指向的是常量池中的#16( #16 = Utf8 Code)位置
1.5.2、属性个数
属性个数占用四个字节对应具体字节码内容如下:
| 00 | 00 | 00 | 2F |
|---|
属性个数中2F十进制为47,对应具体的attribute_info的info内容
1.6、属性表
属性表的结构为:
注意: 从code属性表的第三个开始,也就是max_stack
Code_attribute{
u2 attribute_name_index; 2个字节属性名称索引
u4 attribute_length; 4个字节属性所包含的字节数,注:不包括
attribute_name_index和attribute_length
u2 max_stack; 2个字节,最大操作数栈的深度
u2 max_locals; 2个字节, 最大局部变量表
u4 code_length; 该方法的字节码以及指令码
u1 code[code_length]; 具体指令
u2 exception_table_length; 异常表个数
{
u2 start_pc;
u2 end_pc; 抛出的异常由这个表处理
u2 handler_pc; 表示异常代码的开始处
u2 catch_type; 表示被处理流程的异常类型
} exception_table[exception_table_length]; 异常表结构,用于存放异常信息
u2 attribute_count;
attribute_info attributes[attribute_count];
}
LineNumberTable{
u2 attribute_name_index
u4 attribute_length
u2 line_number_table_length
line_number_info [line_number_table]
}
line_number_info{
u2 字节码对应关系
u2 start_pc
u2 源码中的行号
}
LocalVariableTable{
u2 attribute_name_index
u4 attribute_length
u2 local_variable_table_length
local_variable_info [local_variable_table]
}
local_variable_info{
u2 start_pc
u2 length
u2 name_index
u2 descriptor_index
u2 index
}
下面是属性表中对应的具体的字节码:
| 00 | 01 | 00 | 01 | 00 | 00 | 00 | 05 | 2A | B7 |
|---|---|---|---|---|---|---|---|---|---|
| 00 | 01 | B1 | 00 | 00 | 00 | 02 | 00 | 11 | 00 |
| 00 | 00 | 06 | 00 | 01 | 00 | 00 | 00 | 07 | 00 |
| 12 | 00 | 00 | 00 | 0C | 00 | 01 | 00 | 00 | 00 |
| 05 | 00 | 13 | 00 | 14 | 00 | 00 | **** | **** | **** |
1.6.1、操作数栈深度
操作数栈深度占用两个字节对应字节码:
| 00 | 01 |
|---|
代表当前操作数栈的深度为1,
1.6.2、局部变量表个数
局部变量表深度占用两个字节对应字节码:
| 00 | 01 |
|---|
代表当前局部变量表的个数为1:
1.6.3、字节码以及指令码
u4 code_length; 该方法的字节码以及指令码
u1 code[code_length]; 具体指令
字节码以及指令码占用四个字节对应的内容如下:
| 00 | 00 | 00 | 05 |
|---|
代表字节码以及指令码的长度为5,具体的指令对应的字节码内容如下:
| 2A | B7 | 00 | 01 | B1 |
|---|
2A: 对应的是字节码注记符是aload_0,作用是把当前调用的方法的栈帧中的局部变量表中索引下标为0的值,加载到操作数栈中
B7: 表示的是invokspecial调用父类中的方法,其中00 01 代表指向常量池中的第1个位置
B1: 表示对应字节码指令return
1.6.4、异常表
u2 exception_table_length; 异常表个数
{
u2 start_pc;
u2 end_pc; 抛出的异常由这个表处理
u2 handler_pc; 表示异常代码的开始处
u2 catch_type; 表示被处理流程的异常类型
} exception_table[exception_table_length]; 异常表结构,用于存放异常信息
异常表的个数对应的字节码内容:
| 00 | 00 |
|---|
可以看到当前方法没有异常表
1.6.5、Code属性
u2 attribute_count;
attribute_info attributes[attribute_count];
code属性的个数所对应的字节码内容:
| 00 | 02 |
|---|
表示当前属性存在两个
1.6.6、行表
LineNumberTable{
u2 attribute_name_index
u4 attribute_length
u2 line_number_table_length
line_number_info [line_number_table]
}
属性名称索引: 占用两个字节对应的字节码内容如下
| 00 | 11 |
|---|
对应10进制为17,指向索引下标#17的位置
属性长度: 占用四个字节对应字节码内容如下
| 00 | 00 | 00 | 06 |
|---|
属性长度为6,查看对应的插件内容也是6
指令码和源码映射关系: 对应的字节码如下
line_number_info{
u2 字节码对应关系
u2 start_pc
u2 源码中的行号
}
| 00 | 01 |
|---|
这里只有一对属性关系
指令码和源码具体内容: 对应的字节码如下
| 00 | 00 | 00 | 07 |
|---|
1.6.7、本地方法变量表
LocalVariableTable{
u2 attribute_name_index
u4 attribute_length
u2 local_variable_table_length
local_variable_info [local_variable_table]
}
local_variable_info{
u2 start_pc
u2 length
u2 name_index
u2 descriptor_index
u2 index
}
本地变量表的名称: 占用两个字节对应的字节码如下:
| 00 | 12 |
|---|
指向常量池中#18( #18 = Utf8 LocalVariableTable)的位置,对应的插件的内容
本地变量表中属性的长度: 占用四个字节具体字节码内容如下
| 00 | 00 | 00 | 0C |
|---|
属性长度为12,正好和插件的内容对应上:
本地变量表个数: 占用两个字节对应字节码内容
| 00 | 01 |
|---|
表示当前本地变量表的长度为1
1.6.8、local_variable_info结构:
local_variable_info{
u2 start_pc
u2 length
u2 name_index
u2 descriptor_index
u2 index
}
start_pc: 局部变量生命周期开始的偏移量,占用两个字节对应的字节码内容:
| 00 | 00 |
|---|
可以看到对应的是下标0,查看反编译后的代码和插件内容
length: 作用覆盖的长度,占用两个字节,对应的字节码内容如下
| 00 | 05 |
|---|
对应的长度为5,查看反编译后的内容和插件内容
name_index: 局部变量名称,占用两个字节对应的字节码内容如下:
| 00 | 13 |
|---|
表示对应索引#19( #19 = Utf8 this)的位置,查看对应反编译后的内容和插件的内容,代表着当前this
decp_index: 表示局部变量描述符索引,占用两个字节对应的字节码内容如下:
| 00 | 14 |
|---|
表示指向索引#20( #20 = Utf8 Lcom/yogurt/classParse/AddCode;)的位置,对应反编译后的内容如下
index: 局部变量表在栈帧局部变量表中slot的位置,当这个变量数据类型为64位时(double和long),占用slot的index和index+1两个,对应的字节码内容如下:
| 00 | 00 |
|---|
到此,第一个方法解析完毕!
2、第二个方法
2.1、修饰符:
修饰符占用两个字节对应具体字节码内容如下:
| 00 | 01 |
|---|
根据前面的修饰符的内容可以,当前方法是ACC_PUBLIC,使用插件查看具体内容,确实是public类型的:
2.2、方法名称索引
方法名称索引占用两个字节对应具体字节码内容如下:
| 00 | 15 |
|---|
方法名称索引指向常量池中的#21( #21 = Utf8 add)位置,是一个add方法
2.3、方法描述索引
方法描述索引占用两个字节对应具体字节码内容如下:
| 00 | 16 |
|---|
方法描述索引指向常量池中的#22( #22 = Utf8 (II)I)位置
2.4、属性表个数
属性表个数占用两个字节对应具体字节码内容如下:
| 00 | 01 |
|---|
当前方法属性只有一个
2.5、方法属性
下面是方法属性的结构
attribute_info{
u2 attribute_naem_index; # 指向常量池中的索引
u4 attribute_length;
u1 info[attribute_length];
}
方法属性对应具体字节码内容如下:
| 00 | 10 | 00 | 00 | 00 | 42 |
|---|
2.5.1、方法属性名称
方法属性名称占用二个字节对应具体字节码内容如下:
| 00 | 10 |
|---|
方法属性名称指向的是常量池中的#16( #16 = Utf8 Code)位置
2.5.2、属性个数
属性个数占用四个字节对应具体字节码内容如下:
| 00 | 00 | 00 | 42 |
|---|
属性个数中2F十进制为66,对应具体的attribute_info的info内容
2.6、属性表
属性表的结构为:
注意: 从code属性表的第三个开始,也就是max_stack
Code_attribute{
u2 attribute_name_index; 2个字节属性名称索引
u4 attribute_length; 4个字节属性所包含的字节数,注:不包括
attribute_name_index和attribute_length
u2 max_stack; 2个字节,最大操作数栈的深度
u2 max_locals; 2个字节, 最大局部变量表
u4 code_length; 该方法的字节码以及指令码
u1 code[code_length]; 具体指令
u2 exception_table_length; 异常表个数
{
u2 start_pc;
u2 end_pc; 抛出的异常由这个表处理
u2 handler_pc; 表示异常代码的开始处
u2 catch_type; 表示被处理流程的异常类型
} exception_table[exception_table_length]; 异常表结构,用于存放异常信息
u2 attribute_count;
attribute_info attributes[attribute_count];
}
LineNumberTable{
u2 attribute_name_index
u4 attribute_length
u2 line_number_table_length
line_number_info [line_number_table]
}
line_number_info{
u2 字节码对应关系
u2 start_pc
u2 源码中的行号
}
LocalVariableTable{
u2 attribute_name_index
u4 attribute_length
u2 local_variable_table_length
local_variable_info [local_variable_table]
}
local_variable_info{
u2 start_pc
u2 length
u2 name_index
u2 descriptor_index
u2 index
}
下面是属性表中对应的具体的字节码,长度为66:
| 00 | 02 | 00 | 03 | 00 | 00 | 00 | 04 | 1B | 1C |
|---|---|---|---|---|---|---|---|---|---|
| 60 | AC | 00 | 00 | 00 | 02 | 00 | 11 | 00 | 00 |
| 00 | 06 | 00 | 01 | 00 | 00 | 00 | 0A | 00 | 12 |
| 00 | 00 | 00 | 20 | 00 | 03 | 00 | 00 | 00 | 04 |
| 00 | 13 | 00 | 14 | 00 | 00 | 00 | 00 | 00 | 04 |
| 00 | 17 | 00 | 18 | 00 | 01 | 00 | 00 | 00 | 04 |
| 00 | 19 | 00 | 18 | 00 | 02 | **** | **** | **** | **** |
2.6.1、操作数栈深度
操作数栈深度占用两个字节对应字节码:
| 00 | 02 |
|---|
代表当前操作数栈的深度为2
2.6.2、局部变量表个数
局部变量表深度占用两个字节对应字节码:
| 00 | 03 |
|---|
代表当前局部变量表的个数为3
2.6.3、字节码以及指令码
u4 code_length; 该方法的字节码以及指令码
u1 code[code_length]; 具体指令
字节码以及指令码占用四个字节对应的内容如下:
| 00 | 00 | 00 | 04 |
|---|
代表字节码以及指令码的长度为4,具体的指令对应的字节码内容如下:
| 1B | 1C | 60 | AC |
|---|
对应反编译后的内容
1B: 对应标记符iload_1 ,作用是把当前调用方法的栈帧的局部变量表索引下标为1的变量内容加载到操作数栈的栈顶
1C: 对应标记符iload_2 ,作用是把当前调用方法的栈帧的局部变量表索引下标为2的变量内容加载到操作数栈的栈顶
60: 对应标记符iadd ,作用是把当前操作数栈的两个数出栈,然后做int类型相加把结果在压入栈顶
AC: 对应标记符ireturn,作用是直接返回int类型的内容
2.6.4、异常表
u2 exception_table_length; 异常表个数
{
u2 start_pc;
u2 end_pc; 抛出的异常由这个表处理
u2 handler_pc; 表示异常代码的开始处
u2 catch_type; 表示被处理流程的异常类型
} exception_table[exception_table_length]; 异常表结构,用于存放异常信息
异常表的个数对应的字节码内容:
| 00 | 00 |
|---|
可以看到当前方法没有异常表
2.6.5、Code属性
u2 attribute_count;
attribute_info attributes[attribute_count];
code属性的个数所对应的字节码内容:
| 00 | 02 |
|---|
表示当前属性存在两个分别是如下:
2.6.6、行表
LineNumberTable{
u2 attribute_name_index
u4 attribute_length
u2 line_number_table_length
line_number_info [line_number_table]
}
属性名称索引: 占用两个字节对应的字节码内容如下
| 00 | 11 |
|---|
对应10进制为17,指向#17的位置
属性长度: 占用四个字节对应字节码内容如下
| 00 | 00 | 00 | 06 |
|---|
属性长度为6,查看对应的插件内容也是6,对应后面6个字节码是指令和源码的映射关系
指令码和源码映射关系: 对应的字节码如下
line_number_info{
u2 字节码对应关系
u2 start_pc
u2 源码中的行号
}
| 00 | 01 | 00 | 00 | 00 | 0A |
|---|
从上面的字节码可知,当前长度为6,其中前2个字节码代表字节码和源码的对应关系
| 00 | 01 |
|---|
表示只有一对对应关系
接下来解析剩下的字节码
| 00 | 00 | 00 | 0A |
|---|
其中00 00代表当前起始位置,看反编译的代码和插件的内容可以看到是从0开始
剩下的00 0A代表行号,可知是行号为10的位置
2.6.7、本地方法变量表
占用字节码如下:
LocalVariableTable{
u2 attribute_name_index
u4 attribute_length
u2 local_variable_table_length
local_variable_info [local_variable_table]
}
local_variable_info{
u2 start_pc
u2 length
u2 name_index
u2 descriptor_index
u2 index
}
| 00 | 12 | 00 | 00 | 00 | 20 | 00 | 03 |
|---|
本地变量表的名称: 占用两个字节对应的字节码如下:
| 00 | 12 |
|---|
表示指向索引#18的位置,对应反编译后代码和插件内容如下:
本地变量表中属性的长度: 占用四个字节具体字节码内容如下
| 00 | 00 | 00 | 20 |
|---|
表示当前属性长度为32
本地变量表个数: 占用两个字节对应字节码内容
| 00 | 03 |
|---|
表示当前有三个变量表,对应插件内容,正好是三行
2.6.8、local_variable_info结构:
根据上面的内容可知,有三行局部变量
第一个局部变量表内容:
local_variable_info{
u2 start_pc
u2 length
u2 name_index
u2 descriptor_index
u2 index
}
| 00 | 00 | 00 | 04 | 00 | 13 | 00 | 14 | 00 | 00 |
|---|
start_pc: 局部变量生命周期开始的偏移量,占用两个字节对应的字节码内容:
| 00 | 00 |
|---|
表示当前从下标为0的位置开始
length: 作用覆盖的长度,占用两个字节,对应的字节码内容如下
| 00 | 04 |
|---|
对应的长度为4,查看反编译后的内容和插件内容
name_index: 局部变量名称,占用两个字节对应的字节码内容如下:
| 00 | 13 |
|---|
对应的十进制为19,为下标#19(#19 = Utf8 this),查看反编译后的代码和插件内容如下:
decp_index: 表示局部变量描述符索引,占用两个字节对应的字节码内容如下:
| 00 | 14 |
|---|
对应的十进制的为20,指向下标为#20的索引,查看反编译后的代码和插件内容如下:
index: 局部变量表在栈帧局部变量表中slot的位置,当这个变量数据类型为64位时(double和long),占用slot的index和index+1两个,对应的字节码内容如下:
| 00 | 00 |
|---|
第二个局部变量表内容:
local_variable_info{
u2 start_pc
u2 length
u2 name_index
u2 descriptor_index
u2 index
}
| 00 | 00 | 00 | 04 | 00 | 17 | 00 | 18 | 00 | 01 |
|---|
start_pc: 局部变量生命周期开始的偏移量,占用两个字节对应的字节码内容:
| 00 | 00 |
|---|
当前从下标为0的开始,对应反编译和插件内容如下:
length: 作用覆盖的长度,占用两个字节,对应的字节码内容如下:
| 00 | 04 |
|---|
当前长度为4,对应反编译和插件内容如下:
name_index: 局部变量名称,占用两个字节对应的字节码内容如下:
| 00 | 17 |
|---|
转成十进制为23是对应我们的变量a,对应反编译和插件内容如下:
decp_index: 表示局部变量描述符索引,占用两个字节对应的字节码内容如下:
| 00 | 18 |
|---|
转成十进制为24,对应反编译和插件内容如下:
index: 局部变量表在栈帧局部变量表中slot的位置,当这个变量数据类型为64位时(double和long),占用slot的index和index+1两个,对应的字节码内容如下:
| 00 | 01 |
|---|
对应反编译和插件内容如下:
第二个局部变量表内容:
local_variable_info{
u2 start_pc
u2 length
u2 name_index
u2 descriptor_index
u2 index
}
| 00 | 00 | 00 | 04 | 00 | 19 | 00 | 18 | 00 | 02 |
|---|
start_pc: 局部变量生命周期开始的偏移量,占用两个字节对应的字节码内容:
| 00 | 00 |
|---|
当前从下标为0的开始,对应反编译和插件内容如下
length: 作用覆盖的长度,占用两个字节,对应的字节码内容如下;
| 00 | 04 |
|---|
当前所占长度为4,对应反编译和插件内容如下
name_index: 局部变量名称,占用两个字节对应的字节码内容如下:
| 00 | 19 |
|---|
转成十进制为25是指向变量b,对应反编译和插件内容如下
decp_index: 表示局部变量描述符索引,占用两个字节对应的字节码内容如下:
| 00 | 18 |
|---|
转成十进制为24,对应反编译和插件内容如下
index: 局部变量表在栈帧局部变量表中slot的位置,当这个变量数据类型为64位时(double和long),占用slot的index
和index+1两个,对应的字节码内容如下:
| 00 | 02 |
|---|
当前slot位置为2,对应反编译和插件内容如下
3、第三个方法
3.1、修饰符:
修饰符占用两个字节对应具体字节码内容如下:
| 00 | 09 |
|---|
根据前面的修饰符的内容可以,当前方法是ACC_PUBLIC、ACC_STATIC,使用插件查看具体内容,确实是public、static类型的:
3.2、方法名称索引
方法名称索引占用两个字节对应具体字节码内容如下:
| 00 | 1A |
|---|
方法名称索引指向常量池中的#26( #26 = Utf8 main)位置,是一个main方法
3.3、方法描述索引
方法描述索引占用两个字节对应具体字节码内容如下:
| 00 | 1B |
|---|
方法名称索引指向常量池中的#27( #27 = Utf8 ([Ljava/lang/String;)V)位置,是一个String类型
3.4、属性表个数
属性表个数占用两个字节对应具体字节码内容如下:
| 00 | 01 |
|---|
可知当前属性表只有一个
3.5、方法属性
下面是方法属性的结构
attribute_info{
u2 attribute_naem_index; # 指向常量池中的索引
u4 attribute_length;
u1 info[attribute_length];
}
方法属性对应具体字节码内容如下:
| 00 | 10 | 00 | 00 | 00 | 64 |
|---|
3.5.1、方法属性名称
方法属性名称占用二个字节对应具体字节码内容如下:
| 00 | 10 |
|---|
指向索引#16的位置,对应反编译和插件内容如下:
3.5.2、属性个数
属性个数占用四个字节对应具体字节码内容如下:
| 00 | 00 | 00 | 64 |
|---|
属性个数中64十进制为100,对应具体的attribute_info的info内容
3.6、属性表
属性表的结构为:
注意: 从code属性表的第三个开始,也就是max_stack
Code_attribute{
u2 attribute_name_index; 2个字节属性名称索引
u4 attribute_length; 4个字节属性所包含的字节数,注:不包括
attribute_name_index和attribute_length
u2 max_stack; 2个字节,最大操作数栈的深度
u2 max_locals; 2个字节, 最大局部变量表
u4 code_length; 该方法的字节码以及指令码
u1 code[code_length]; 具体指令
u2 exception_table_length; 异常表个数
{
u2 start_pc;
u2 end_pc; 抛出的异常由这个表处理
u2 handler_pc; 表示异常代码的开始处
u2 catch_type; 表示被处理流程的异常类型
} exception_table[exception_table_length]; 异常表结构,用于存放异常信息
u2 attribute_count;
attribute_info attributes[attribute_count];
}
LineNumberTable{
u2 attribute_name_index
u4 attribute_length
u2 line_number_table_length
line_number_info [line_number_table]
}
line_number_info{
u2 字节码对应关系
u2 start_pc
u2 源码中的行号
}
LocalVariableTable{
u2 attribute_name_index
u4 attribute_length
u2 local_variable_table_length
local_variable_info [local_variable_table]
}
local_variable_info{
u2 start_pc
u2 length
u2 name_index
u2 descriptor_index
u2 index
}
下面是属性表中对应的具体的字节码,长度为100:
00030002 00000028 BB000259 B7000307 1006B600 043CB200 05BB0006 59B70007 1208B600 091BB600 0AB6000B B6000CB1 00000002 00110000 000E0003 0000000E 000E000F 00270010 00120000 00160002 00000028 001C001D 0000000E 001A0015 00180001
| 00 | 03 | 00 | 02 | 00 | 00 | 00 | 28 | BB | 00 |
|---|---|---|---|---|---|---|---|---|---|
| 02 | 59 | B7 | 00 | 03 | 07 | 10 | 06 | B6 | 00 |
| 04 | 3C | B2 | 00 | 05 | BB | 00 | 06 | 59 | B7 |
| 00 | 07 | 12 | 08 | B6 | 00 | 09 | 1B | B6 | 00 |
| 0A | B6 | 00 | 0B | B6 | 00 | 0C | B1 | 00 | 00 |
| 00 | 02 | 00 | 11 | 00 | 00 | 00 | 0E | 00 | 03 |
| 00 | 00 | 00 | 0E | 00 | 0E | 00 | 0F | 00 | 27 |
| 00 | 10 | 00 | 12 | 00 | 00 | 00 | 16 | 00 | 02 |
| 00 | 00 | 00 | 28 | 00 | 1C | 00 | 1D | 00 | 00 |
| 00 | 0E | 00 | 1A | 00 | 15 | 00 | 18 | 00 | 01 |
3.6.1、操作数栈深度
操作数栈深度占用两个字节对应字节码:
| 00 | 03 |
|---|
当前操作数栈的深度为3,可以查看我们的反编译内容:
3.6.2、局部变量表个数
局部变量表深度占用两个字节对应字节码:
| 00 | 02 |
|---|
当前局部变量表的大小为2,可以查看对应的反编译内容:
3.6.3、字节码以及指令码
u4 code_length; 该方法的字节码以及指令码
u1 code[code_length]; 具体指令
字节码以及指令码占用四个字节对应的内容如下:
| 00 | 00 | 00 | 28 |
|---|
表示当前存在40个指令
| BB | 00 | 02 | 59 | B7 | 00 | 03 | 07 | 10 | 06 |
|---|---|---|---|---|---|---|---|---|---|
| B6 | 00 | 04 | 3C | B2 | 00 | 05 | BB | 00 | 06 |
| 59 | B7 | 00 | 07 | 12 | 08 | B6 | 00 | 09 | 1B |
| B6 | 00 | 0A | B6 | 00 | 0B | B6 | 00 | 0C | B1 |
如图:
BB: 代表new指令, 00 02表示当前指令指向常量池中的#2位置
59: 代表dup指令
B7: 代表invokespecial指令,其中00 03表示指向常量池中#3的位置
07: 代表iconst_4指令
10: 代表bipush指令,其中06代表具体的值
B6: 代表invokevirtual指令,其中00 04 代表指向常量池中下标为#4的位置
3C: 代表istore_1指令
B2: 代表getstatic指令,其中00 05 为指向具体的常量池中的下标索引
BB: 代表new指令,其中00 06为指向常量池中具体的索引位置
59: 代表dup指令
B7: 代表invokespecial指令,其中00 07指向常量池中具体的索引位置
12: 代表ldc指令,其中08指向常量池中索引位置为#8的地址
B6: 代表指令invokevirtual,其中00 09表示指向常量池中#9的位置
1B: 代表iload_1指令
B6: 代表invokevirtual指令,其中00 0A表示指向常量池中索引下标为#10的位置
B6: 代表invokevirtual指令,其中00 0B表示指向常量池中下标为#11的位置
B6: 代表invokevirtual指令,其中00 0C表示指向常量池中下标为#12的位置
B1: 代表return指令
3.6.4、异常表
u2 exception_table_length; 异常表个数
{
u2 start_pc;
u2 end_pc; 抛出的异常由这个表处理
u2 handler_pc; 表示异常代码的开始处
u2 catch_type; 表示被处理流程的异常类型
} exception_table[exception_table_length]; 异常表结构,用于存放异常信息
异常表的个数对应的字节码内容:
| 00 | 00 |
|---|
当前没有异常表
3.6.5、Code属性
u2 attribute_count;
attribute_info attributes[attribute_count];
code属性的个数所对应的字节码内容:
| 00 | 02 |
|---|
当前存在两个属性:
3.6.6、行表
LineNumberTable{
u2 attribute_name_index
u4 attribute_length
u2 line_number_table_length
line_number_info [line_number_table]
}
属性名称索引: 占用两个字节对应的字节码内容如下
| 00 | 11 |
|---|
当前指向常量池中索引位置为#17的地方
属性长度: 占用四个字节对应字节码内容如下
| 00 | 00 | 00 | 0E |
|---|
当长度为14,如图所示:
指令码和源码映射关系: 对应的字节码如下
line_number_info{
u2 字节码对应关系
u2 start_pc
u2 源码中的行号
}
| 00 | 03 | 00 | 00 | 00 | 0E |
|---|
前2个字节码代表字节码和源码的对应关系
| 00 | 03 |
|---|
当前存在3对关系
第一个对应关系:
| 00 | 00 | 00 | 0E |
|---|
表示当前起始位置是0, 行号为14
第二个对应关系:
| 00 | 0E | 00 | 0F |
|---|
表示当前起始位置为: 14, 行号为15
第三个对应关系:
| 00 | 27 | 00 | 10 |
|---|
表示当前起始位置为: 39, 行号为16
3.6.7、本地方法变量表
占用字节码如下:
LocalVariableTable{
u2 attribute_name_index
u4 attribute_length
u2 local_variable_table_length
local_variable_info [local_variable_table]
}
local_variable_info{
u2 start_pc
u2 length
u2 name_index
u2 descriptor_index
u2 index
}
| 00 | 12 | 00 | 00 | 00 | 16 | 00 | 02 |
|---|
本地变量表的名称: 占用两个字节对应的字节码如下:
| 00 | 12 |
|---|
表示指向常量池中#18的位置,如图:
本地变量表中属性的长度: 占用四个字节具体字节码内容如下
| 00 | 00 | 00 | 16 |
|---|
当前属性长度为22,如图:
本地变量表个数: 占用两个字节对应字节码内容
| 00 | 02 |
|---|
当前本地变量存在两个,如图:
3.6.8、local_variable_info结构:
根据上面的内容可知,有两行局部变量
第一个局部变量表内容:
local_variable_info{
u2 start_pc
u2 length
u2 name_index
u2 descriptor_index
u2 index
}
| 00 | 00 | 00 | 28 | 00 | 1C | 00 | 1D | 00 | 00 |
|---|
start_pc: 局部变量生命周期开始的偏移量,占用两个字节对应的字节码内容:
| 00 | 00 |
|---|
当前从0开始,如图:
length: 作用覆盖的长度,占用两个字节,对应的字节码内容如下
| 00 | 28 |
|---|
当前长度为40,如图:
name_index: 局部变量名称,占用两个字节对应的字节码内容如下:
| 00 | 1C |
|---|
表示指向常量池中#28的位置,如图:
decp_index: 表示局部变量描述符索引,占用两个字节对应的字节码内容如下:
| 00 | 1D |
|---|
示指向常量池中#29的位置,如图:
index: 局部变量表在栈帧局部变量表中slot的位置,当这个变量数据类型为64位时(double和long),占用slot的index和index+1两个,对应的字节码内容如下:
| 00 | 00 |
|---|
当前指向slot位置0处
第二个局部变量表内容:
local_variable_info{
u2 start_pc
u2 length
u2 name_index
u2 descriptor_index
u2 index
}
| 00 | 0E | 00 | 1A | 00 | 15 | 00 | 18 | 00 | 01 |
|---|
start_pc: 局部变量生命周期开始的偏移量,占用两个字节对应的字节码内容:
| 00 | 0E |
|---|
表示当前从14开始, 如图:
length: 作用覆盖的长度,占用两个字节,对应的字节码内容如下
| 00 | 1A |
|---|
表示当前长度为26,如图:
name_index: 局部变量名称,占用两个字节对应的字节码内容如下:
| 00 | 15 |
|---|
表示当前指向常量池中下标为#21的位置,如图:
decp_index: 表示局部变量描述符索引,占用两个字节对应的字节码内容如下:
| 00 | 18 |
|---|
表示当前指向常量池中索引下标为#24的位置,如图:
index: 局部变量表在栈帧局部变量表中slot的位置,当这个变量数据类型为64位时(double和long),占用slot的index和index+1两个,对应的字节码内容如下:
| 00 | 01 |
|---|
表示当前slot位置为1,如图:
14、class文件-SourceFile
attribute_count(class文件的属性) // 只存在一个属性
{
"attribute_name_index":"u2 指向常量池中SourceFile,属性名",
"attribute_length":"u4 表示属性的长度",
"sourceFile_index":"u2 表示源文件的索引
}
0001001E 00000002 001F
| 00 | 01 | 00 | 1E | 00 | 00 | 00 | 02 | 00 | 1F |
|---|
上面字节码内容为SourceFile
1、class文件属性:
占用两个字节,下面为具体的字节码内容:
| 00 | 01 |
|---|
表示当前存在一个属性
2、属性名SourceFile
用两个字节,下面为具体的字节码内容:
| 00 | 1E |
|---|
表示指向常量池中下标为#30的位置,如图:
3、属性长度
占用两个字节,下面为具体的字节码内容:
| 00 | 00 | 00 | 02 |
|---|
表示当前长度为2,如图:
4、属性文件
占用两个字节,下面为具体的字节码内容:
| 00 | 1F |
|---|
表示指向常量池中下标为#31的位置,如图: