jvm字节码分析
一、预备过程:
1.源文件
SJQ.java
1 package com.soecode.lyf.java; 2 3 public class SJQ { 4 public static final int paraA=10; 5 private static final String paraString="sunjignqin"; 6 private void myfun(int a, String b){ 7 System.out.println(a+b); 8 } 9 // class Inner1{ 10 // public class Inner100{ 11 // 12 // } 13 // } 14 // public final nimin fun(){ 15 // class Inner2{ // class 不能有修饰符 16 // 17 // } 18 // return new nimin(10){ 19 // 20 // }; 21 // } 22 // 23 // public static void main(String[] args) { 24 // SJQ sjq = new SJQ(); 25 // sjq.fun(); 26 // } 27 }
2.class文件
编译SJQ.java生成对应的class文件:javac -d . SJQ.java
SJQ.class
CAFEBABE 00000034 00310A00 0A001A09 001B001C 07001D0A 0003001A 0A000300 1E0A0003 001F0A00 0300200A 00210022
07002307 00240100 05706172 61410100 01490100 0D436F6E 7374616E 7456616C 75650300 00000A01 000A7061 72615374
72696E67 0100124C 6A617661 2F6C616E 672F5374 72696E67 3B080025 0100063C 696E6974 3E010003 28295601 0004436F
64650100 0F4C696E 654E756D 62657254 61626C65 0100056D 7966756E 01001628 494C6A61 76612F6C 616E672F 53747269
6E673B29 5601000A 536F7572 63654669 6C650100 08534A51 2E6A6176 610C0012 00130700 260C0027 00280100 176A6176
612F6C61 6E672F53 7472696E 67427569 6C646572 0C002900 2A0C0029 002B0C00 2C002D07 002E0C00 2F003001 0018636F
6D2F736F 65636F64 652F6C79 662F6A61 76612F53 4A510100 106A6176 612F6C61 6E672F4F 626A6563 7401000A 73756E6A
69676E71 696E0100 106A6176 612F6C61 6E672F53 79737465 6D010003 6F757401 00154C6A 6176612F 696F2F50 72696E74
53747265 616D3B01 00066170 70656E64 01001C28 49294C6A 6176612F 6C616E67 2F537472 696E6742 75696C64 65723B01
002D284C 6A617661 2F6C616E 672F5374 72696E67 3B294C6A 6176612F 6C616E67 2F537472 696E6742 75696C64 65723B01
0008746F 53747269 6E670100 1428294C 6A617661 2F6C616E 672F5374 72696E67 3B010013 6A617661 2F696F2F 5072696E
74537472 65616D01 00077072 696E746C 6E010015 284C6A61 76612F6C 616E672F 53747269 6E673B29 56002100 09000A00
00000200 19000B00 0C000100 0D000000 02000E00 1A000F00 10000100 0D000000 02001100 02000100 12001300 01001400
00001D00 01000100 0000052A B70001B1 00000001 00150000 00060001 00000003 00020016 00170001 00140000 00350003
00030000 0019B200 02BB0003 59B70004 1BB60005 2CB60006 B60007B6 0008B100 00000100 15000000 0A000200 00000700
18000800 01001800 00000200 19
3.javap查看class文件信息
javap -verbose ./com/soecode/lyf/java/SJQ.class
➜ java git:(master) ✗ javap -verbose ./com/soecode/lyf/java/SJQ.class Classfile /Users/sunjingqin/IdeaProjects/ssm/src/test/java/com/soecode/lyf/java/com/soecode/lyf/java/SJQ.class Last modified 2022-2-10; size 733 bytes MD5 checksum 14e3069204f301adb03fbaa9289888b1 Compiled from "SJQ.java" public class com.soecode.lyf.java.SJQ minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #10.#26 // java/lang/Object."<init>":()V #2 = Fieldref #27.#28 // java/lang/System.out:Ljava/io/PrintStream; #3 = Class #29 // java/lang/StringBuilder #4 = Methodref #3.#26 // java/lang/StringBuilder."<init>":()V #5 = Methodref #3.#30 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; #6 = Methodref #3.#31 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #7 = Methodref #3.#32 // java/lang/StringBuilder.toString:()Ljava/lang/String; #8 = Methodref #33.#34 // java/io/PrintStream.println:(Ljava/lang/String;)V #9 = Class #35 // com/soecode/lyf/java/SJQ #10 = Class #36 // java/lang/Object #11 = Utf8 paraA #12 = Utf8 I #13 = Utf8 ConstantValue #14 = Integer 10 #15 = Utf8 paraString #16 = Utf8 Ljava/lang/String; #17 = String #37 // sunjignqin #18 = Utf8 <init> #19 = Utf8 ()V #20 = Utf8 Code #21 = Utf8 LineNumberTable #22 = Utf8 myfun #23 = Utf8 (ILjava/lang/String;)V #24 = Utf8 SourceFile #25 = Utf8 SJQ.java #26 = NameAndType #18:#19 // "<init>":()V #27 = Class #38 // java/lang/System #28 = NameAndType #39:#40 // out:Ljava/io/PrintStream; #29 = Utf8 java/lang/StringBuilder #30 = NameAndType #41:#42 // append:(I)Ljava/lang/StringBuilder; #31 = NameAndType #41:#43 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #32 = NameAndType #44:#45 // toString:()Ljava/lang/String; #33 = Class #46 // java/io/PrintStream #34 = NameAndType #47:#48 // println:(Ljava/lang/String;)V #35 = Utf8 com/soecode/lyf/java/SJQ #36 = Utf8 java/lang/Object #37 = Utf8 sunjignqin #38 = Utf8 java/lang/System #39 = Utf8 out #40 = Utf8 Ljava/io/PrintStream; #41 = Utf8 append #42 = Utf8 (I)Ljava/lang/StringBuilder; #43 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #44 = Utf8 toString #45 = Utf8 ()Ljava/lang/String; #46 = Utf8 java/io/PrintStream #47 = Utf8 println #48 = Utf8 (Ljava/lang/String;)V { public static final int paraA; descriptor: I flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL ConstantValue: int 10 public com.soecode.lyf.java.SJQ(); 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 } SourceFile: "SJQ.java"
4.通过idea反编译SJQ.class
直接在idea打开SJQ.class文件,得到如下结果:
1 // 2 // Source code recreated from a .class file by IntelliJ IDEA 3 // (powered by FernFlower decompiler) 4 // 5 6 package com.soecode.lyf.java; 7 8 public class SJQ { 9 public static final int paraA = 10; 10 private static final String paraString = "sunjignqin"; 11 12 public SJQ() { 13 } 14 15 private void myfun(int var1, String var2) { 16 System.out.println(var1 + var2); 17 } 18 }
二、分析过程
每个ClassFile文件对应于一个如下所示的ClassFile结构(<<Java虚拟机规范.Java SE 8版.pdf>> 4.1节):
1、magic
魔数:固定值0xCAFEBABE。
对应class文件中:CAFEBABE。
2、minor_version、major_version
minor_version、major_version分别表示class文件的副、主版本号。满足版本要求的class文件才可以被对应的java虚拟机接受。
对应class文件中:0000 0034。
3、constant_pool_count
常量池计数器:constant_pool_count=常量池表中成员数目+1。
对应class文件中:0031。
4、constant_pool
接下来继续分析class文件的字节码。
0A000A001A:0A对应十进制值10,对应常量为CONSTANT_Methodref_info。000A指向声明方法的类描述符CONSTANT_class_info的索引项。001A指向名称及类型描述符CONSTANT_NameAndTypes_info的索引项。000A与001A在常量池Constant pool对应的值为:
#10 = Class #36 // java/lang/Object #26 = NameAndType #18:#19 // "<init>":()V
09001B001C:09对应十进制值9,对应常量为CONSTANT_Filedref_info。001B指向声明字段的类或者接口类描述符CONSTANT_class_info的索引项。001A指向字段描述符CONSTANT_NameAndTypes_info的索引项。对应的值为:
#27 = Class #38 // java/lang/System
#28 = NameAndType #39:#40 // out:Ljava/io/PrintStream;
07001D:07对应十进制7,对应常量为CONSTANT_class_info。001D对应的值为:
#29 = Utf8 java/lang/StringBuilder
0A0003001A
0A0003001E
0A0003001F
0A00030020
0A00210022
070023
070024
0100057061726141:01对应十进制1,对应常量CONSTANT_utf8_info。0005指的是length为5,后面的五个字节为7061726141对应字符的十六进制编码。原始字符为:paraA。
以下同理:
01000149:I
01000D43 6F6E7374 616E7456 616C7565
03000000 0A
01000A 70617261 53747269 6E67
0100 124C6A61 76612F6C 616E672F 53747269 6E673B
08 0025
0100 063C696E 69743E
01 00032829 56
010004 436F6465
01000F4C 696E654E 756D6265 72546162 6C65
0100 056D7966 756E
0100 1628494C 6A617661 2F6C616E 672F5374 72696E67 3B2956:(ILjava/lang/String;)V
01 000A536F 75726365 46696C65:SourceFile
省略下面的分析:
01000853 4A512E6A 6176610C 00120013 0700260C 00270028 0100176A 6176612F 6C616E67 2F537472 696E6742 75696C64 65720C00 29002A0C 0029002B 0C002C00 2D07002E 0C002F00 30010018 636F6D2F 736F6563 6F64652F 6C79662F 6A617661 2F534A51 0100106A 6176612F 6C616E67 2F4F626A 65637401 000A7375 6E6A6967 6E71696E 0100106A 6176612F 6C616E67 2F537973 74656D01 00036F75 74010015 4C6A6176 612F696F 2F507269 6E745374 7265616D 3B010006 61707065 6E640100 1C284929 4C6A6176 612F6C61 6E672F53 7472696E 67427569 6C646572 3B01002D 284C6A61 76612F6C 616E672F 53747269 6E673B29 4C6A6176 612F6C61 6E672F53 7472696E 67427569 6C646572 3B010008 746F5374 72696E67 01001428 294C6A61 76612F6C 616E672F 53747269 6E673B01 00136A61 76612F69 6F2F5072 696E7453 74726561 6D010007 7072696E 746C6E01 0015284C 6A617661 2F6C616E 672F5374 72696E67 3B2956
5、access_flags
访问标志:表示某个类或者接口的访问权限及属性。
0021
6、this_class
类的全限定名
0009
#9 = Class #35 // com/soecode/lyf/java/SJQ
7、super_class
000A
#10 = Class #36 // java/lang/Object
8、interfaces_count:
当前累实现的接口数目。当前类0000表示没有实现接口。
9、interfaces:
表示接口的全限定名索引。共有interfaces_count个。当前类SJQ.java接口为空。
10、field_count
表示类变量和实例变量总的个数。当前类是0002,表示有两个字段。
11、fields
fields的长度为fields_count,field_info是一个复合结构。
filed_info: { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
1)第一个字段:
0019:public static final对应值得累加和。 000B: #11 = Utf8 paraA 000C:#12 = Utf8 I 0001:attribute_count为1 000D:#13 = Utf8 ConstantValue 00000002:ConstantValue_attribute结构的attribute_length项的值固定位2 000E:#14 = Integer 10
注意:在field_info结构的属性中,最多只能有一个ConstantValue属性。ConstantValue属性的格式如下:
ConstantValue_attribute{ u2 attribute_name_index; u4 attribute_length; u2 constantvalue_index; }
2)第二个字段
001A:private static final 000F: #15 = Utf8 paraString 0010:#16 = Utf8 Ljava/lang/String; 0001: 000D:#13 = Utf8 ConstantValue 00000002:固定值 0011:#17 = String #37 // sunjignqin
12、methods_count
表示方法的个数。当前类0002表示有2个方法。
13、methods
methods是一个长度为method_count的method_info结构。
method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
其中attribute_info对应的是【code】,所以Code_attribute结构如下:
Code_attribute { //Code_attribute包含某个方法、实例初始化方法、类或接口初始化方法的Java虚拟机指令及相关辅助信息 u2 attribute_name_index; u4 attribute_length; u2 max_stack; //用来给出当前方法的操作数栈在方法执行的任何时间点的最大深度 u2 max_locals; //用来给出分配在当前方法引用的局部变量表中的局部变量个数 u4 code_length; //给出当前方法code[]数组的字节数 u1 code[code_length];//给出了实现当前方法的Java虚拟机代码的实际字节内容 (这些数字代码实际对应一些Java虚拟机的指令) u2 exception_table_lentgh; 0000 0 //异常的信息 { u2 start_pc; //这两项的值表明了异常处理器在code[]中的有效范围,即异常处理器x应满足:start_pc≤x≤end_pc u2 end_pc; //start_pc必须在code[]中取值,end_pc要么在code[]中取值,要么等于code_length的值 u2 handler_pc; //表示一个异常处理器的起点 u2 catch_type; //表示当前异常处理器需要捕捉的异常类型。为0,则都调用该异常处理器,可用来实现finally。 } exception_table[exception_table_lentgh]; 在本类中大括号里的结构为空 u2 attribute_count; 0002 2 表示该方法的其它附加属性,本类有1个 attribute_info attributes[attributes_count]; 000A、000B LineNumberTable、LocalVariableTable }
1)分析第一个方法
0001:access_flag:public 0012:anme_index:#18 = Utf8 <init> 0013:descriptor_index: #19 = Utf8 ()V 0001:attribute_count:1 0014:#20 = Utf8 Code 0000 001D:attribute_length:29 0001:max_stack 0001:max_locals 0000 0005:code_length 2AB7 0001B1:code[code_length]:42、183、0、1、177
0000:exception_table_length;
0001:attribute_count
0015: #21 = Utf8 LineNumberTable
00000006:attribute_length
0001:line_number_table_length
0000:start_pc
0003:line_number
2)第二个方法
00 02001600 17000100 14000000 35000300 03000000 19B20002 BB000359 B700041B B600052C B60006B6 0007B600 08B10000 00010015 0000000A 00020000 00070018 0008
14、aattribute_count
表示整个class文件的附件属性。当前类为0001
15、attributes
class文件附加属性,当前类为0018,指向常量池#24:SourceFile。SourceFile结构如下:
SourceFile_attribute { u2 attribute_name_index; 0018 SourceFile u4 attribute_length; 00000002 2 u2 sourcefile_index; 0019 #25 = Utf8 SJQ.java。//表示本class文件是由SJQ.java编译来的 }
至此、完整的class文件的字节码分析完毕。
参考书籍:<<Java虚拟机规范.Java SE 8版.pdf>>
参考博客:https://www.cnblogs.com/lilei94/p/9744331.html