本来想回顾下类加载机制,但是感觉学习类加载机制之前应该先了解下类文件结构,所以就有了这篇文章。来次够先学习起来祈祷明天获得一个面试机会呀!!!

首先思考一个问题:

    是不是只有java编译器才能完成 java到 .class字节码文件的过程?(突然想来个原谅色)

    第一眼看到这个问题时候我好像楞了几秒,额,有点不知所措。其实是个很简单的问题,之前学习过的Groovy不就可以编译为class文件么。虽然学习过但是一直没有使用好像已经忘差不多了,只记得不用写分号有点爽。过几天重新学习下,如果还有疑问请看下图:

为什么javaclass会变成java file怎么改回来 java怎么变成class文件_class文件结构


Scala、jython、JRuby、Groovy都可以编译成class文件,到这里我好像对。class文件有点感觉了

     window环境下想要研究字节码文件首先我们需要一个可以查看字节码文件的编译器,这个选择就很多了你可以在idea上安装HexView,也可以使用notePad或者binary Viewer这样的工具。自行安装下载吧.

为什么javaclass会变成java file怎么改回来 java怎么变成class文件_class文件结构_02


第一次看我已经懵逼了,讲道理有点晕。但是有什么办法呢,只能硬着头皮一点一点看了。反正是写给自己看的就不画表格了。。。

为什么javaclass会变成java file怎么改回来 java怎么变成class文件_class文件结构_03


     Class文件: 是一组以8位字节为基础单位的二进制流,当遇到需要8位字节以上空间的数据项时,则会按照高位在前的方式分隔成若干个8位字节进行存储。

Class文件由无符号数和表构成。

     无符号数: 以u1、u2、u4、u8分别代表1个字节、2个字节、4个字节和8个字节的无符号数,可以用来描述数字、索引引用、数量值、按照UTF-8编码构成的字符串值。

     表: 由多个无符号数或其他表作为数据项构成的复杂数据类型,所有表都习惯性地以“_info”结尾。

我们先看一下class文件的总体结构再一一分析::

为什么javaclass会变成java file怎么改回来 java怎么变成class文件_class文件结构_04

1.魔数(U4): 每个class文件的前四个字节,它的作用是确认此文件是否为能被虚拟机接受的class文件。所以这也是为什么魔数固定存在于class文件的前四个字节。同时它的值也是固定的(ca-fe-ba-be ),很好记忆就是咖啡宝贝么哈哈哈。

2.次版本号:

3.主版本号:



4.常量池计数器(U2): 00-5A:10+5*16=90, 表示常量池数为90-1=89。为什么常量池数不是90而是89呢? ,因为常量池的计数器并不是从0开始计数的,而是从1开始。这里好像不太容易理解,本菜鸟暂时理解为像保留的关键字一样这里java在最初设计时也有考虑。在当时指定class文件规范的时候,为了满足某些指向常量池的索引值的数据在特定情况下能够表达。此时就可以不引用任何一个常量池,从而用保留的0来表达学习常量池我们首先需要一张对照表,如下。

为了区分下面换个原谅色:
ca fe ba be 20 20 20 34 20 5a 0a 20 0a 20 4a 09 20 09 20 4b 09 20 09 20 4c 09 20 09 20 4d 09 20 09 20 4e 09 20 09 20 4f 09 20 09 20 50 0a 20 51 20 52 07 20 53 07 20 54 07 20 55 07 20 56 01 20 0d 53 74 75 44 65 74 61 69 6c 56 69 65 77 01 20 0c 49 6e 6e 65 72 43 6c 61 73 73 65 73 07 20 57 01 20 0b 53 74 75 53 69 6d 70 56 69 65 77 01 20 03 73 6e 6f 01 20 10 4c 6a 61 76 61 2f 6c 61 6e 67 2f 4c 6f 6e 67 3b 01 20 19 52 75 6e 74 69 6d 65 56 69 73 69 62 6c 65 41 6e 6e 6f 74 61 74 69

0A = 10: 这个时候我们就需要根据数值对照下面常量池表查找对应常量了,10的值对应为 CONSTANT_Methodref_info。从表格中可以看出它后面对应的两个索引即为000A 与 004A 转换为10进制分别为10和74。
随所以10与74的意义分别为,声明方法的类描述符CONSTANT_Class_info的索引,与名称及类型描述符CONSTANT_NameAndType的索引
以此类推一直到常量池的结尾,上面提到的89个感觉好多。。。。。就不一一推倒了。

到这里可能会好奇了我们费尽九牛二虎之力查表的目的是什么??

哈哈哈当然是为了摆脱菜鸟称号了

接下来我们需要用javap命令反编译拿到我们的字节码文件与我们之前看到的机器码进行相互印证
我上面测试类反编译后字节码如下(贴一小部分上来):

5.常量:

E:\project\learning2\graduation_project\target\classes\com\chihai\graduation_project\entity>javap -v Stu.class
Classfile /E:/project/learning2/graduation_project/target/classes/com/chihai/graduation_project/entity/Stu.class
  Last modified 2019-9-1; size 3073 bytes
  MD5 checksum f0dcf3623c3133d43a9fdad54d79fa66
  Compiled from "Stu.java"
public class com.chihai.graduation_project.entity.Stu implements java.io.Serializable
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #10.#74        // java/lang/Object."<init>":()V
   #2 = Fieldref           #9.#75         // com/chihai/graduation_project/entity/Stu.sno:Ljava/lang/Long;
   #3 = Fieldref           #9.#76         // com/chihai/graduation_project/entity/Stu.sname:Ljava/lang/String;
   #4 = Fieldref           #9.#77         // com/chihai/graduation_project/entity/Stu.ssex:Ljava/lang/String;
   #5 = Fieldref           #9.#78         // com/chihai/graduation_project/entity/Stu.sbirthday:Ljava/util/Date;
   #6 = Fieldref           #9.#79         // com/chihai/graduation_project/entity/Stu.sclass:Ljava/lang/String;
   #7 = Fieldref           #9.#80         // com/chihai/graduation_project/entity/Stu.address:Lcom/chihai/graduation_project/entity/Address;
   #8 = Methodref          #81.#82        // java/lang/String.trim:()Ljava/lang/String;
   #9 = Class              #83            // com/chihai/graduation_project/entity/Stu
  #10 = Class              #84            // java/lang/Object
  #11 = Class              #85            // java/io/Serializable
  #12 = Class              #86            // com/chihai/graduation_project/entity/Stu$StuDetailView
  #13 = Utf8               StuDetailView
  #14 = Utf8               InnerClasses
  #15 = Class              #87            // com/chihai/graduation_project/entity/Stu$StuSimpView
  #16 = Utf8               StuSimpView
  #17 = Utf8               sno
  #18 = Utf8               Ljava/lang/Long;
  #19 = Utf8               RuntimeVisibleAnnotations
  #20 = Utf8               Ljavax/validation/constraints/NotEmpty;
  #21 = Utf8               message
  #22 = Utf8               学生编号不能为空
  #23 = Utf8               Lcom/fasterxml/jackson/annotation/JsonView;
  #24 = Utf8               value
  #25 = Utf8               Lcom/chihai/graduation_project/entity/Stu$StuSimpView;
  #26 = Utf8               RuntimeVisibleTypeAnnotations
  #27 = Utf8               sname
  #28 = Utf8               Ljava/lang/String;
  #29 = Utf8               ssex
  #30 = Utf8               Lcom/chihai/graduation_project/entity/Stu$StuDetailView;
  #31 = Utf8               sbirthday
  #32 = Utf8               Ljava/util/Date;
  #33 = Utf8               Lorg/springframework/format/annotation/DateTimeFormat;
  #34 = Utf8               pattern
  #35 = Utf8               yyyy-mm-dd HH-mm-ss
  #36 = Utf8               sclass
  #37 = Utf8               address
  #38 = Utf8               Lcom/chihai/graduation_project/entity/Address;
  #39 = Utf8               <init>
  #40 = Utf8               (Ljava/lang/Long
  y/Address;)V
  #41 = Utf8               Code
  #42 = Utf8               LineNumberTable
  #43 = Utf8               LocalVariableTable
  #44 = Utf8               this
  #45 = Utf8               Lcom/chihai/graduation_project/entity/Stu;
  #46 = Utf8               ()V
  #47 = Utf8               getAddress
  #48 = Utf8               ()Lcom/chihai/graduation_project/entity/Address;
  #49 = Utf8               setAddress
  #50 = Utf8               (Lcom/chihai/graduation_project/entity/Address;)V
  #51 = Utf8               getSno
  #52 = Utf8               ()Ljava/lang/Long;
  #53 = Utf8               setSno
  #54 = Utf8               (Ljava/lang/Long;)V
  #55 = Utf8               getSname
  #56 = Utf8               ()Ljava/lang/String;
  #57 = Utf8               setSname
  #58 = Utf8               (Ljava/lang/String;)V
  #59 = Utf8               StackMapTable
  #60 = Class              #83            // com/chihai/graduation_project/entity/Stu
  #61 = Class              #88            // java/lang/String
  #62 = Class              #88            // java/lang/String
  #63 = Utf8               getSsex
  #64 = Utf8               setSsex
  #65 = Utf8               getSbirthday
  #66 = Utf8               ()Ljava/util/Date;
  #67 = Utf8               setSbirthday
  #68 = Utf8               (Ljava/util/Date;)V
  #69 = Utf8               getSclass
  #70 = Utf8               setSclass
  #71 = Class              #88            // java/lang/String
  #72 = Utf8               SourceFile
  #73 = Utf8               Stu.java
  #74 = NameAndType        #39:#46        // "<init>":()V
  #75 = NameAndType        #17:#18        // sno:Ljava/lang/Long;
  #76 = NameAndType        #27:#28        // sname:Ljava/lang/String;
  #77 = NameAndType        #29:#28        // ssex:Ljava/lang/String;
  #78 = NameAndType        #31:#32        // sbirthday:Ljava/util/Date;
  #79 = NameAndType        #36:#28        // sclass:Ljava/lang/String;
  #80 = NameAndType        #37:#38        // address:Lcom/chihai/graduation_project/entity/Address;
  #81 = Class              #88            // java/lang/String
  #82 = NameAndType        #89:#56        // trim:()Ljava/lang/String;
  #83 = Utf8               com/chihai/graduation_project/entity/Stu
  #84 = Utf8               java/lang/Object
  #85 = Utf8               java/io/Serializable
  #86 = Utf8               com/chihai/graduation_project/entity/Stu$StuDetailView
  #87 = Utf8               com/chihai/graduation_project/entity/Stu$StuSimpView
  #88 = Utf8               java/lang/String
  #89 = Utf8               trim

这个时候好像发现了什么,如果联系上文我们所对照得到的数字在字节码中我们都能找到其相对应的代码指令。version: 52有没有很熟悉呢这就是我们机器码所对应的主版本号0034.我相信看到这里大家跟我一样找到了一点感觉。Constant pool:拉到最下面我们可以清楚的看到#89 正好验证了我们上面的解析不多不少89个。忘了的朋友们可以返回去看一下-------飞雷神之术

为什么javaclass会变成java file怎么改回来 java怎么变成class文件_java_05


篇幅原因常量池我们就先到这里。我们直接跳到最后一个常量,继续往下看(这里有个问题密麻麻们的机器码中怎么能直接找到第89个常量在哪呢?仔细观察我们最后结尾#89 = Utf8 只需要找到最右边对应的字符就可以找到常量池结尾所对应的的行数)

53 74 72 69 6e 67 01 20 04 74 72 69 6d 00 21 00 String…trim.!. 返回上面字节码看一下就明白其中原理了

为什么javaclass会变成java file怎么改回来 java怎么变成class文件_java_06


6.访问标志:

为什么javaclass会变成java file怎么改回来 java怎么变成class文件_字节码_07


这里我们需要思考0021是什么,其实远没有我们想象中复杂。真相就是1+20,对照图表中我们不难发现当们一个类声明为 public class没有其它的时候就是0021(public class Stu implements Serializable )

接下来我们继续往下看为了方便我把一段机器码粘贴过来如下:

53 74 72 69 6e 67 01 00 04 74 72 69 6d 00 21 00  
09 00 0a 00 01 00 0b 00 06 00 02 00 11 00 12 00

访问标志,之后的排序为类索引,父类索引,接口索引集合。那我们来验证下到底是不是这样:

类索引(00-09):

我们去字节码文件中找到对应#9常量-------------------》 #9 = Class #83 // com/chihai/graduation_project/entity/Stu 不偏不倚正好对应我们的类索引

类索引(00-0A):

#10 = Class #84 // java/lang/Object 因为我们的demo类没有继承其他类所以父类自动为Object

接口计数器(00-01):

因为我们的类只实现了Serializable接口所以计数器为1,也完全正确

接口1(00-0B):

#11 = Class #85 // java/io/Serializable

6.字段表集合:

字段表计数器(00-06):

刚好对应下面6个字段:
#17 = Utf8 sno
#27 = Utf8 sname
#29 = Utf8 ssex
#31 = Utf8 sbirthday
#36 = Utf8 sclass
#37 = Utf8 address

字段信息结构表():

为什么javaclass会变成java file怎么改回来 java怎么变成class文件_常量池_08

字段访问标志:

为什么javaclass会变成java file怎么改回来 java怎么变成class文件_class文件结构_09


7.字段表结构::

因为字段表与方发表几乎是一样的所以我们就挑一个看就可以了

为什么javaclass会变成java file怎么改回来 java怎么变成class文件_class文件结构_10


为什么javaclass会变成java file怎么改回来 java怎么变成class文件_class文件结构_11


为什么javaclass会变成java file怎么改回来 java怎么变成class文件_java_12

8.方法表集合:

00 00 0c 00 01 00 00 00 05 00 2c 00 2d 00 00 00
01 00 2f 00 30 00 01 00 29 00 00 00 2f 00 01 00

方法表的查找顺序为:
    1.access_flags ---------2.name _index --------3.descriptor_index-------4.attribute_count
    5.对照attribute_info(U2,U4,U1*length) --------6.name_index代表着字段的简单名称
    7.对应U4 attribute_length

需要说明的是从第一行末尾00开始的是我们方法表集合,所以我们来看0001-002f-0030代表的是什么

0001代表public
002f为47对应字节码常量: #47 = Utf8 getAddress
0030为48对应字节码常量:()Lcom/chihai/graduation_project/entity/Address;
0001:有一个属性表
对照attribute_info(U2,U4,U1length)
0029为41对应字节码常量: #41 = Utf8 Code
0000-002f对应字节码常量47: 47
u1 每一行16个U1 后面2个 ,2+2*16+13 = 47,即往下3行,到第3行的倒数第2个U1,这一块区域即为我们方法块中的信息

到这里我们就可以看出其代表的正式我们类中的:public Address getAddress(){}方法

47个U1包含的方法代码块信息对照下表:

为什么javaclass会变成java file怎么改回来 java怎么变成class文件_class文件结构_13

直接截图了复制notpad不知为什么过来数字总会变化,从紫色开始。。。这个是紫色么接上面的机器码

max_stack:方法的栈深:0001代表当前方法栈深为1
max_locals:方法的本地变量数量:0001 我们与字节码对照一下
args_size=1 :代表方法参数个数(为什么明明没有参数却为1,因为代表默认this,如果为static则为0,这就使我们为什么可以使用this的原因)
code_length:是U4所以是0000 0005 代表代码行数

剩下的即为方法字节码,此时我们需要对照虚拟机字节码指令表来解释。

给个传送门大家慢慢研究吧

我们简单看一下:2a------- 0x2a aload_0 将第一个引用类型本地变量推送至栈顶
    b4---------0xb4 getfield 获取指定类的实例域, 并将其压入栈顶

#7 = Fieldref #9.#80 // com/chihai/graduation_project/entity/Stu.address:Lcom/chihai/graduation_project/entity/Address;

因为demo没有抛出异常,所以字节码中没有异常表

存在方法体中:

为什么javaclass会变成java file怎么改回来 java怎么变成class文件_class文件结构_14

本方法throw

为什么javaclass会变成java file怎么改回来 java怎么变成class文件_字节码_15

LineNumberTable:
line 50: 0
50是指向我们java文件中的行数 ,0则是指向字节码code中行数

完全与我们下面的字节码一致

public com.chihai.graduation_project.entity.Address getAddress();
    descriptor: ()Lcom/chihai/graduation_project/entity/Address;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #7                  // Field address:Lcom/chihai/graduation_project/entity/Address;
         4: areturn
      LineNumberTable:
        line 50: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/chihai/graduation_project/entity/Stu;

6.属性表集合:

为什么javaclass会变成java file怎么改回来 java怎么变成class文件_java_16

LocalVariableTable: 本地变量表

Signature:在我们JVM中是伪泛型,编译时会进行泛型擦除。改用Signature标识

SourceFile: “Stu.java”

ConstantValues:通知虚拟机自动为静态变量赋值

为什么javaclass会变成java file怎么改回来 java怎么变成class文件_常量池_17


为什么javaclass会变成java file怎么改回来 java怎么变成class文件_字节码_18


为什么javaclass会变成java file怎么改回来 java怎么变成class文件_常量池_19

为什么javaclass会变成java file怎么改回来 java怎么变成class文件_常量池_20

其实并不难只是有点麻烦写个简单的类对照表一步步分析还是可以的,本菜鸟就先到这里了!!!!要爱惜头发后面就不分析了