一个Java程序是怎样运行起来的【class解析全过程】

2022-11-29 10:25:25 浏览数 (1)

首先编写一测试程序

代码语言:javascript复制
public class Test {
	
	public static void main(String[] args){
		System.out.println("HelloWorld");
	}
    
}

执行javac Test.java 得到Test.class文件(编译过程有点复杂,这里先不看) 执行java Test,控制台输出"test",想要弄清楚java程序是怎么运行起来首先得了解清楚class文件

看下Test.class里究竟是什么东西,class文件的内容如下:

上图中都是以16进制表示,接下来挨个分析其中的内容表示什么意思。class文件中存储的数据可以参考下表:

1、magic 魔数

CA FE BA BE

魔数,确定该文件是否是虚拟机可以接受的文件 2、class文件版本信息

00 00 00 33

class文件的版本号,51表示jdk1.7.0

3、常量池

3.1常量池入口

00 1D

常量池数量为29-1=28,每个类只有一个常量池

常量池中放了字符串,常量值,类名称,字段名,方法名等,反编译下Test.class,看看常量池中存放了哪些东西

代码语言:javascript复制
Constant pool:
   #1 = Methodref          #6.#15         //  java/lang/Object."<init>":()V
   #2 = Fieldref           #16.#17        //  java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #18            //  test
   #4 = Methodref          #19.#20        //  java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #21            //  Test
   #6 = Class              #22            //  java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               Test.java
  #15 = NameAndType        #7:#8          //  "<init>":()V
  #16 = Class              #23            //  java/lang/System
  #17 = NameAndType        #24:#25        //  out:Ljava/io/PrintStream;
  #18 = Utf8               test
  #19 = Class              #26            //  java/io/PrintStream
  #20 = NameAndType        #27:#28        //  println:(Ljava/lang/String;)V
  #21 = Utf8               Test
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V

常量池中的项目类型有: CONSTANT_Utf8_info      tag标志位为1,   UTF-8编码的字符串,比如类或接口的全限定名,参数名等 CONSTANT_Integer_info  tag标志位为3, int整型字面量 CONSTANT_Float_info     tag标志位为4, float浮点型字面量 CONSTANT_Long_info     tag标志位为5, long长整形字面量 CONSTANT_Double_info  tag标志位为6, double双精度字面量 CONSTANT_Class_info    tag标志位为7, 类或接口的符号引用,指向包含字符串字面值的CONSTANT_Utf8表 CONSTANT_String_info    tag标志位为8,字符串类型的字面量,指向包含字符串字面值的CONSTANT_Utf8表 CONSTANT_Fieldref_info  tag标志位为9,  字段的符号引用,指向包含该字段所属类名的CONSTANT_Utf8表 CONSTANT_Methodref_info  tag标志位为10,类中方法的符号引用,指向包含该方法所属类型的CONSTANT_Utf8表 CONSTANT_InterfaceMethodref_info tag标志位为11, 接口中方法的符号引用 CONSTANT_NameAndType_info  tag 标志位为12,字段和方法的名称以及类型的符号引用

3.2常量池内容

接上,继续分析class中的内容,参照 jvm官方文档 ,看下常量池中究竟是什么东西

常量池1-----0A 00 06 00 0F   //

  1,0A---tag为10,表示第一个常量类型为CONSTANT_Methodref,参照官方文档,CONSTANT_Methodref的结构为

代码语言:javascript复制
CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

所以后面跟了4个字节   2,00 06---声明当前方法类描述符索引值为6     //  java/lang/Object

  3,00 0F---当前方法的名称和类型索引值为15  //  "<init>":()V

所以,结合上文中反编译出的内容来看,这几个16进制翻译过来正好是

代码语言:javascript复制
#1 = Methodref          #6.#15         //  java/lang/Object."<init>":()V

常量池2----09 00 10 00 11

  1,09---tag为9,类型为CONSTANT_Fieldref

  2,00 10---声明当前方法类描述符索引值为16 // java/lang/System

  3,00 11---字段描述符的名称和类型索引值为17 //  out:Ljava/io/PrintStream;

这几个16进制翻译过来正好是

#2 = Fieldref           #16.#17        //  java/lang/System.out:Ljava/io/PrintStream;

常量池3---08 00 12

   1,08---tag为8,类型为CONSTANT_String,根据官方文档,其结构为

代码语言:javascript复制
CONSTANT_String_info {
    u1 tag;
    u2 string_index;
}

所以后面跟了两个字节

   2,00 12---声明当前String值所在的索引值为18

当前16进制翻译过来,表示

代码语言:javascript复制
#3 = String             #18            //  test

常量池4---0A  00 13 00 14,对照着上面的分析,

代码语言:javascript复制
#4 = Methodref          #19.#20        //  java/io/PrintStream.println:(Ljava/lang/String;)V

常量池5---07 00 15

   1,07---tag为7,类型为CONSTANT_Class,根据官方文档,其结构为

代码语言:javascript复制
CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}

   2,00 15----当前类名称的索引值为21

代码语言:javascript复制
#5 = Class              #21            //  Test

常量池6---07 00 16,对照上面的分析

代码语言:javascript复制
#6 = Class              #22            //  java/lang/Object

常量池7---01 00 06 3C 69 6E 69 74 3E

  1,01---tag为1,类型为CONSTANT_Utf8,根据官方文档

代码语言:javascript复制
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}
代码语言:javascript复制
  2,06---表示字符串的长度为6

  3,3C 69 6E 69 74 3E ---字符串<init>

代码语言:javascript复制
#7 = Utf8               <init>

常量池8---01 00 03 28 29 56

常量池9---01 00 04 43 6F 64 65

常量池10---01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65

常量池11---01 00 04 6D 61 69 6E

常量池12---01 00 16 28 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56

常量池13---01 00 0A 53 6F 75 72 63 65 46 69 6C 65

常量池14---01 00 09 54 65 73 74 2E 6A 61 76 61

代码语言:javascript复制
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               Test.java

常量池15---0C 00 07 00 08

  1,0C---tag为11,类型为CONSTANT_NameAndType,参照jvm官方文档,其结构为

代码语言:javascript复制
CONSTANT_NameAndType_info {
    u1 tag;
    u2 name_index;
    u2 descriptor_index;
}

  2,00 07---该字段或方法名称常量索引值为7,即

代码语言:javascript复制
#7 = Utf8               <init>

 3,00 08---该字段或方法描述符常量索引值为8 ,即

代码语言:javascript复制
#8 = Utf8               ()V

常量池16---07 00 17

常量池17---0C 00 18 00 19

常量池18---01 00 04 74 65 73 74

常量池19---07 00 1A

常量池20---0C 00 1B 00 1C

常量池21---01 00 04 54 65 73 74

常量池22---01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74

常量池23---01 00 10 6A 61 76 6A 2F 6C 61 6E 67 2F 53 79 73 74 65 6D

常量池24---01 00 03 6F 75 74

常量池25---01 00 15 4C 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B

常量池26---01 00 13 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D

常量池27---01 00 07 70 72 69 6E 74 6C 6E

常量池28---01 00 15 28 4 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56

代码语言:javascript复制
  #16 = Class              #23            //  java/lang/System
  #17 = NameAndType        #24:#25        //  out:Ljava/io/PrintStream;
  #18 = Utf8               test
  #19 = Class              #26            //  java/io/PrintStream
  #20 = NameAndType        #27:#28        //  println:(Ljava/lang/String;)V
  #21 = Utf8               Test
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V

到此常量池结束

4、访问标志access_flags

00 21----Test类的访问标志,参照官方文档,访问标志有

0x0021 = 0x0020|0x0001,即ACC_PUBLIC和ACC_SUPER为真,ACC_PUBLIC好理解,ACC_SUPER这是什么鬼,翻看官方文档,原文如下:

The ACC_SUPER flag indicates which of two alternative semantics is to be expressed by the invokespecial instruction (§invokespecial) if it appears in this class. Compilers to the instruction set of the Java Virtual Machine should set the ACC_SUPER flag.

The ACC_SUPER flag exists for backward compatibility with code compiled by older compilers for the Java programming language. In Oracle’s JDK prior to release 1.0.2, the compiler generated ClassFile access_flags in which the flag now representing ACC_SUPER had no assigned meaning, and Oracle's Java Virtual Machine implementation ignored the flag if it was set.

为了兼容之前的jdk版本,在jdk1.0.2之后这个编译出来的为真

5,类索引,父类索引,接口索引

接下来就是类索引,父类索引,接口索引

00 05------类索引值为#5 

代码语言:javascript复制
#5 = Class              #21            //  Test

00 06-----父类索引值为#6

代码语言:javascript复制
#6 = Class              #22            //  java/lang/Object

00 00----类没有实现接口,接口数为0,所以后面没有接口信息

6、字段

00 00----当前类有0个字段

7、方法,指令

00 02----当前类有两个方法,参照官方文档,方法的结构如下:

代码语言:javascript复制
method_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

方法1:00 01 00 07 00 08 00 01 

      ----00 01:access_flags=0x0001=ACC_PUBLIC,方法的访问标志如下表:

 ---00 07:name_index=#7----->#7 = Utf8               <init>,可以看出该方法为构造函数

   ---00 08:descriptor_index=#8------>#8 = Utf8               ()V

   ---00 01:attributes_count=1,所以紧随其后就是attribute_info部分,根据官方文档,其结构如下:

代码语言:javascript复制
Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack;
    u2 max_locals;
    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 attributes_count;
    attribute_info attributes[attributes_count];
}

 00 09 00 00 00 1D 00 01 00 01 00 00 00 05   //非指令部分      ---00 09:attribute_name_index=#9---------->#9 = Utf8               Code

     ---00 00 00 1D:attribute_length=29,所以整个属性表的长度为29 6=35,见官方文档说明:The value of the attribute_length item indicates the length of the attribute, excluding the initial six bytes.

     ---00 01:max_stack=1

     ---00 01:max_locals=1

     ---00 00 00 05:code_length=5

紧接着就是方法1的指令部分: 2A B7 00 01 B1

     ---2A:aload_0 ,

     ---B7 00 01 ->invokespecial #1,调用超类构造方法

     ---B1--->return

方法1的Exception:

00 00:方法没有throw异常

方法1的attribute count:

00 01://方法1最后有一个属性块,其结构如下:

代码语言:javascript复制
LineNumberTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 line_number_table_length;
    {   u2 start_pc;
        u2 line_number;	
    } line_number_table[line_number_table_length];
}

00 0A 00 00 00 06 00 01 00 00 00 01

     ---00 0A:attribute_name_index=#10---->#10 = Utf8               LineNumberTable

     ---00 00 00 06:attribute_lenght=6

     ---00 01:line_number_table_length=1,表示这个LineNumberTable中有一条记录

     ---00 00 00 01:表示Test.java的第一行代码对应指令0--->0: aload_0

方法2:00 09 00 0B 00 0C 00 01

    ---00 09:access_flags=0x0008|0x0001=ACC_STATIC|ACC_PUBLIC

    ---00 0B:name_index=#11------>#11 = Utf8               main

    ---00 0C:descriptor_index=#12----->#12 = Utf8               ([Ljava/lang/String;)V

    ---00 01:arrtibutes_count=1,紧接着是attribute_info

方法2的code,非指令部分:

00 09 00 00 00 25 00 02 00 01 00 00 00 09

    ---00 09:attribute_name_index=#9----->#9 = Utf8               Code

    ---00 00 00 25:attribute_length=37,所以整个属性表的长度为43

    ---00 02:max_stack=2

    ---00 01:max_locals=1

    ---00 00 00 09:code_length=17

方法2的code,指令部分

B2 00 02----->getstatic #2:获取指定类的静态域,并且压入到栈顶,#2表示#2 = Fieldref           #16.#17        //  java/lang/System.out:Ljava/io/PrintStream;

12 03--->ldc #3,将“test”常量值从常量池中压入到栈顶

B6 00 04---->invokervirtual  #4,调用实例方法,#4 = Methodref          #19.#20        //  java/io/PrintStream.println:(Ljava/lang/String;)V,即println方法

B1---->return

方法2的Exception:

00 00 ----->没有定义throw

方法2的attribute_count:

00 01 //方法最后有个属性

方法2的LineNumberTable:

00 0A 00 00 00 0A 00 02

     ----00 0A:attribute_name_index=#10------>#10 = Utf8               LineNumberTable

     ----00 00 00 0A:attribute_length=10

     ----00 02:line_number_table_lenght=2,表示lineNumberTable中有2条记录

00 00 00 04:Test.java第4行对应指令0 --->getstatic     #2

00 08 00 05:Test.java第5行对应指令8----->8: return

8.sourceFile属性

00 01:当前class文件包含1个attribute_info

00 0D 00 00 00 02 00 0E

     ---00 0D:attribute_name_index=#13---->#13 = Utf8               SourceFile

     ---00 00 00 02:attribute_length=2

     ---00 0E:sourcefile_index=#14---->#14 = Utf8               Test.java

至此,class文件中的内容分析完毕!

0 人点赞