美文网首页
.java文件编译过程和执行过程分析以及计算机简单认识

.java文件编译过程和执行过程分析以及计算机简单认识

作者: 坑王老薛 | 来源:发表于2018-09-08 15:58 被阅读555次

    一个.java文件的"沉浮"之路

    本文全为原创内容,转载请注明出处:https://www.jianshu.com/p/af78a314c6fc

    1:引言:

    由于昨天的网络问题,导致分享做的不是特别好。所以老薛这里写了一份分
    享的笔记,希望可以帮助那些没有听课的同学稍稍弥补一下。
    

    昨天的分享大概分为一下几个模块来讲解的:

    i:分享初衷
    ii:分享内容大纲
    iii:建议
    iv:一个.java文件的沉浮之路
        a:什么是计算机?
        b:计算机发展历史
        c:.java文件如何变为.class文件
        d:.class文件如何被JVM加载,加载内容如何解析
        e:jvm和操作系统和底层硬件的简单交互。
    

    2:分享初衷:

    很多人遇到一个很尴尬的问题:一看就会,一问就懵,一敲就傻。
    新技术层出不穷,到底学习什么?如何取舍。
    业务代码很多,编写代码不够优,不知道如何增加自己的硬编码实例。
    每天都很忙,不知道如何利用自己的碎片化时间武装自己的知识体系。
    

    基于以上内容,希望能够找到一些技术。分享给大家,而这些技术必须具备如下特征:

    可能实用价值不高,但是希望能够在未来很长一段时间对你而言都有帮助

    可以很好的和目前的编码统一起来,不能由于新的技术加入,导致和目前的学习工作没有关系

    3:分享内容大纲

    基于刚才的内容,我大概在自己有限的学习数据库中整理了一下。我会在以下内容开始分享:

    ‘瞎’侃计算机组成与系统结构
    ‘胡’说数据结构和算法
    ‘嗨’聊计算机网络
    实战编码:
        你真的会写代码吗? 如何写出优雅高效的代码!
        什么是面向对象?能够通过面向对象的方式去看待问题,而非将对象作为一个Data
        手撕系列!能够通过java 写一些好玩的内容。比如服务器,jvm,Struts,Spring等
    

    4:建议

    大家也能看到我这里所有的内容都是加了一个字,所以我尽自己的最大能力把这个东西分享的好玩一点。并且也希望大家意识到,我和大家分享的内容只是我的理解,千万不能全信。

    我希望如果你真的想和我一起增强自己的知识脉络,最好能做到以下几件事情:

    存疑+坚持+广而告之+参与感

    1:存疑  任何知识传播都是会存在差异性的。所以千万不能全信,要有自己的主观判断。
    但是基于28法则,大概率我聊的80%内容你应该是要信的。嘿嘿~~
    2:坚持  这个就不谈了,一句话总结,世界上最难的事情就是坚持,最简单的事情就是放弃。
    3:广而告之  我希望我分享的内容你能够有一个落地,笔记,博客,一定要形成一个书面的内容。
    4:参与感   千万记得,免费的东西是最贵的,因为耗费了你的时间,所以我会努力做的更好,而
    如果你也认可这件事情,请一定要参与进来,而不是作为一个旁观者。
    

    如果这些你都能做到,那么就和我一起并肩前行吧!😁

    5:.java文件的沉浮之路

    接下来进入到这次分享的主要内容,看看小J同学在执行过程中都经历了什么!😊

    编写一个.java文件然后依次编译,执行。过程如下:

    5-1:编写一个.java文件

    编写一个.java文件

    5-2:通过javac和java指令执行

    这个运行过程就不赘述了,我们直接上图:

    java跨平台原理

    5-3:揭秘第一部分.java文件如何变为.class文件

    编译器遵守什么规则,将.java文件编译为.class文件

    5-3-1:编译规则:

    编译过程

    5-3-2:词法分析:

    .java文件编译为.class文件时,先进行词法分析 如下图:

    词法分析

    总结:这里着重解析.java文件编写的第5行代码,可以将例子后的代码理解为java中的第5行代码。
    在进行词法分析时,会将内容进过线性分析,变成一张符号表。符号表为上图中的右侧内容。右侧的表格内容由三部分构成:

    1: 唯一表示 1,2,3 类似符号表中的地址编号
    2: 变量名称 比如:标识符position,操作符号 + 等等
    3: 变量的值
    

    由词法分析我们将整个.java文件中的内容变成了一张表。接下来:

    5-3-2:语法分析:

    词法分析变为一张符号表,继续语法分析

    语法分析

    总结:通过形成的符号表,会生成一颗语法二叉树。在这里有一个很有意思的东西,就是我们观察二叉树,发现符号作为了根节点,而且+号的节点在*号的节点之上。这里解析时倒叙解析。那么为什么*号会在+号节点之下,其实如果你要定义一个语言时,一定要告知这个语言一定是一个形式化的,这个形式化的方式可以通过BNF定义。在形式化的定义规则里,*号的优先级别高于+号。

    `BNF`巴科斯范式(BNF: Backus-Naur Form 的缩写)。引入一种形式化符号来描述给定语言的
    语法。就是通过BNF来定义语言的语法,按照定义的规则去形成编译时的语法分析。
        扩展:
            在双引号中的字("word")代表着这些字符本身。而double_quote用来代表双引号。
            在双引号外的字(有可能有下划线)代表着语法部分。
            尖括号( < > )内包含的为必选项。
            方括号( [ ] )内包含的为可选项。
            大括号( { } )内包含的为可重复0至无数次的项。
            竖线( | )表示在其左右两边任选一项,相当于"OR"的意思。
            ::= 是“被定义为”的意思。
    这部分内容主要涉及编译原理,有兴趣可以自行参考:
    

    参考博文

    5-3-3:java的bnf定义:

    java的bnf定义

    总结:大家可以根据上面的bnf扩展语法,我们发现java的bnf定义如果要声明一个类class_declaration被这样定义:<modifier> 需要编写一个修饰符,修饰符的定义:modifier:=="public"|"private"...,这里我们可知定义类的时候必须时这些关键词,关键词已经给出来了。如果你写的class的修饰符不是modifier定义的词,那么编译时语法就会报错。依次类推。“class”是必须要写的,同理如果定义类是不写则编译语法报错。在到后面的identifier,也是有对应定义的值:identifier:=="a..z,$,_"<"...." 。其实这个不就是java规范中对于标识符的定义吗?不能以数字开头。其实要编写一门语言。必须要有形式化的bnf定义。

    5-3-4:语义分析:

    语义分析,注意计算机的语义分析一定要知道,计算机语言不能存在二意性。这个和自然语言不通。

    语义分析

    总结:在语义分析是会做类型检查,控制流检查,类型检查等等。我们回想之前的java程序,在进行*发运算时,由于rate时float类型,而60时一个int类型的字面值。此时会将60这个int类型转换为float,其实类型转换是在编译时发生的。回想我们编写程序时的基本数据类型的类型转换错误,是不是发生在编译阶段呢?

    5-3-5:中间代码生成,优化以及生产对应的目标文件:

    产生最后的目标文件

    执行过程

    总结:在整个执行过程中,我们发现一个程序编译为字节码文件之后,其实就是一大堆的二进制数据。这里字节码展示是通过16进制查看的。然后进过jvm加载,解析,在jvm中构建一个运行时结构,然后开始执行。我们接下来就编写程序,以及通过反汇编指令去深究一下.class文件中到底有哪些内容。

    5-4-2-1:将我们之前编写的Demo.java编译之后进行读取:

    注意:通过流的方式将Demo.class读取进来,且按照16进制显示数据如下

    读取16进制内容读取16进制内容

    下图是16进制对应一览表

    16进制对应一览表16进制对应一览表

    总结:

    1: u:代表无符号位,u4代表无符号4个字节用来代表魔数。意思凭什么能够确定当前文件是.class呢?
    由魔数确定。也就意味着.class文件的魔数为`cafebabe`,俗称`咖啡宝贝`。你也可以读取一下
    jpg或者其他类型的文件的前4个字节是什么。
    2: 后续的u2,无符号位2个字节代表次版本号,那就是以为当前次版本号为0
    3: 后续的u2,无符号位2个字节代表主版本号,那么这里是是00 35,也就是53,也就是jdk9.
        JDK各个版本对应关系
        JDK 1.10= 54 JDK 1.9 = 53 JDK 1.8 = 52 JDK 1.7 = 51 JDK 1.6 = 50 
        JDK 1.5 = 49 JDK 1.4 = 48 JDK 1.3 = 47 JDK 1.2 = 46 JDK 1.1 = 45
    4:扩展一下,这里采用的是大端模式,关于大小端我之前有聊过。大家也可以参考资料再看看。
    其实采用大端有个地方特别好,这样我们再解析这个16进制内容就稍微方便一点,直接可以省略开头为
    0的字节。
    5: 后续的u2,无符号2个字节代表常量池个数,注意这里的常量池和我们一般意义下的内存中的常量池
    还是有区别的,这里的常量池是包括所有字面常量,比如变量,类名,方法名等等。这里是00 30,那么
    

    也就也为者有48个常量,往后数48个字节,都代表的是常量值。这里注意,48要加上本身常量池的个数 也会占空间,也就以为着一共只有47个常量。
    6: 再往后的u2,代表的是访问标志,该类的修饰符,以及是否可以使用比如多态,如果可以使用那么意 味着就可以使用invokespecial,这个是java字节码调用指令,代表可以调用私有实例方法,构造器以 及使用super调用父类的实例方法等。剩下的依次类推。
    大小端资料参考

    通过javap -c -v反汇编,查看内容:
    以下内容中两个`中间的内容是老薛写的注释

    Classfile /Users/iongst/Desktop/Demo.class  `class文件所在地址`
      Last modified 2018年9月8日; size 884 bytes `最后一次修改时间以及大小`
      MD5 checksum 8619092419ab6d93efe90de9a29f5e36 `md5值`
      Compiled from "Demo.java" `编译自那个文件`
    public class Demo   `类的声明`
      minor version: 0  `次版本号`
      major version: 53 `主版本号 jdk9`
      flags: (0x0021) ACC_PUBLIC, ACC_SUPER `访问标志,acc_public 代表是public修饰,acc_super 代表可以使用invokespecial `
      this_class: #7                          // Demo `thisclass thisclass指向了#7的地址,可以看看#7是什么`
      super_class: #8                         // java/lang/Object
      interfaces: 0, fields: 0, methods: 2, attributes: 3 `对照进制一览表,分别代表接口个数为0,没有实现接口
      fields字段个数为0 方法个数2个,属性有3个。问题2:这里的fields和attributes代表什么含义?`
    Constant pool:      `常量池`
       #1 = Methodref          #8.#17         // java/lang/Object."<init>":()V
       #2 = Float              3.1f
       #3 = Float              60.0f
       #4 = Fieldref           #18.#19        // java/lang/System.out:Ljava/io/PrintStream;
       #5 = InvokeDynamic      #0:#23         // #0:makeConcatWithConstants:(F)Ljava/lang/String;
       #6 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
       #7 = Class              #26            // Deme `存储了#26 继续往下找`
       #8 = Class              #27            // java/lang/Object
       #9 = Utf8               <init>
      #10 = Utf8               ()V
      #11 = Utf8               Code
      #12 = Utf8               LineNumberTable
      #13 = Utf8               main
      #14 = Utf8               ([Ljava/lang/String;)V
      #15 = Utf8               SourceFile
      #16 = Utf8               Demo.java
      #17 = NameAndType        #9:#10         // "<init>":()V
      #18 = Class              #28            // java/lang/System
      #19 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
      #20 = Utf8               BootstrapMethods
      #21 = MethodHandle       6:#31          // REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
      #22 = String             #32            // 计算结果是:\u0001
      #23 = NameAndType        #33:#34        // makeConcatWithConstants:(F)Ljava/lang/String;
      #24 = Class              #35            // java/io/PrintStream
      #25 = NameAndType        #36:#37        // println:(Ljava/lang/String;)V
      #26 = Utf8               Demo         `就存储了类名,问题1:为什么要存储类名?`
      #27 = Utf8               java/lang/Object
      #28 = Utf8               java/lang/System
      #29 = Utf8               out
      #30 = Utf8               Ljava/io/PrintStream;
      #31 = Methodref          #38.#39        // java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
      #32 = Utf8               计算结果是:\u0001
      #33 = Utf8               makeConcatWithConstants
      #34 = Utf8               (F)Ljava/lang/String;
      #35 = Utf8               java/io/PrintStream
      #36 = Utf8               println
      #37 = Utf8               (Ljava/lang/String;)V
      #38 = Class              #40            // java/lang/invoke/StringConcatFactory
      #39 = NameAndType        #33:#44        // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
      #40 = Utf8               java/lang/invoke/StringConcatFactory
      #41 = Class              #46            // java/lang/invoke/MethodHandles$Lookup
      #42 = Utf8               Lookup
      #43 = Utf8               InnerClasses
      #44 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
      #45 = Class              #47            // java/lang/invoke/MethodHandles
      #46 = Utf8               java/lang/invoke/MethodHandles$Lookup
      #47 = Utf8               java/lang/invoke/MethodHandles
      `常量池个数一共有47个,所以虽然我们查看是48个是包含本身常量池的1的所以是48`
    {
      public Demo();
        descriptor: ()V
        flags: (0x0001) 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 1: 0
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: (0x0009) ACC_PUBLIC, ACC_STATIC
        Code:
          stack=3, locals=4, args_size=1
             0: bipush        10
             2: istore_1
             3: ldc           #2                  // float 3.1f
             5: fstore_2
             6: iload_1
             7: i2f
             8: fload_2
             9: ldc           #3                  // float 60.0f
            11: fmul
            12: fadd
            13: fstore_3
            14: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
            17: fload_3
            18: invokedynamic #5,  0              // InvokeDynamic #0:makeConcatWithConstants:(F)Ljava/lang/String;
            23: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            26: return
          LineNumberTable:
            line 3: 0
            line 4: 3
            line 5: 6
            line 6: 14
            line 7: 26
    }
    SourceFile: "Demo.java"
    InnerClasses:
      public static final #42= #41 of #45;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
    BootstrapMethods:
      0: #21 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
        Method arguments:
          #22 计算结果是:\u0001
    

    文末总结:

    大家如果真的能耐着性子把这个文章看完,在我看来真的很厉害了。后面也埋了一些坑,比如后面的方法调用,我这里一共添加了108号反编译之后的代码,后面很多代码老薛也没有讲解,这个作为后续内容。那么我们是不是可以这样理解,如果我们将一个.class文件读取进来之后,按照对照一览表开始解析,只要我们能够分析出来运行时数据区,我们完全是可以自己写一个jvm的。这是没有问题哦。

    如果大家有什么问题,大家可以在文末或者是微信私聊老薛。老薛微信 lukun0402

    本章内容参考书籍资料:《编译原理》 《深入理解计算机系统》 《码农翻身》

    课后作业:

    • 我们知道cpu是个快速设备,内存稍次之,磁盘排第三,如果你走网络会更慢,那么整个执行过程其实是通过最低速设备决定的,你有什么比较好的办法能够解决这个问题吗?
    • 给大家布置了数据结构的系统,如果兴趣的同学可以做一做。作业地址。也可以复制链接:https://github.com/iongst/dataStructure
    • 在作业中也留了一些作业,编写数据结构时,在老薛写的接口中,已经留了思考的彩蛋,大家努力查找吧。

    相关文章

      网友评论

          本文标题:.java文件编译过程和执行过程分析以及计算机简单认识

          本文链接:https://www.haomeiwen.com/subject/tiikgftx.html