HelloWorld引发的思考
一、手写源程序
我们打开任何一本Java学习的书籍,都会告诉我们用记事本或者其他的文本编辑器写一个简单的小程序,然后javac 类名,进行编译,会在同一文件下生成一个同名的.calss文件;我们这里先怒写一个HelloWorld.java,代码如下:
public class HelloWorld { public static void main(String[] args){ System.out.println("Hello World!"); } }
|
.class文件反序列化,代码如下:
package com.tao.study.one; public class HelloWorld { public HelloWorld() { } public static void main(String[] args) { System.out.println("Hello World!"); } }
我们用cmd命令打开我们编译后的.class文件,cmd的命令为:javap -v 类名,HelloWorld.class的内部代码如下:
Classfile /E:/DataBase/Idea/JavaStudy/out/production/JavaStudy/com/tao/study/one/HelloWorld.class Last modified 2017-12-3; size 570 bytes MD5 checksum e34e8917fa1c76751b4ea9a2f491d17a Compiled from "HelloWorld.java" public class com.tao.study.one.HelloWorld minor version: 0 //副版本号 major version: 52 //编译器版本号 == jdk1.8 flags: ACC_PUBLIC, ACC_SUPER //常量池 Constant pool: #1 = Methodref #6.#20 // java/lang/Object."<init>":()V #2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #23 // Hello World! #4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #26 // com/tao/study/one/HelloWorld #6 = Class #27 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lcom/tao/study/one/HelloWorld; #14 = Utf8 main #15 = Utf8 ([Ljava/lang/String;)V #16 = Utf8 args #17 = Utf8 [Ljava/lang/String; #18 = Utf8 SourceFile #19 = Utf8 HelloWorld.java #20 = NameAndType #7:#8 // "<init>":()V #21 = Class #28 // java/lang/System #22 = NameAndType #29:#30 // out:Ljava/io/PrintStream; #23 = Utf8 Hello World! #24 = Class #31 // java/io/PrintStream #25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V #26 = Utf8 com/tao/study/one/HelloWorld #27 = Utf8 java/lang/Object #28 = Utf8 java/lang/System #29 = Utf8 out #30 = Utf8 Ljava/io/PrintStream; #31 = Utf8 java/io/PrintStream #32 = Utf8 println #33 = Utf8 (Ljava/lang/String;)V { public com.tao.study.one.HelloWorld(); 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 6: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/tao/study/one/HelloWorld; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello World! 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 8: 0 line 9: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String; } SourceFile: "HelloWorld.java"
二、提出疑问
看到上面的结果,我们的第一感觉肯定是,woc,怎么这么神奇,这里面都发生了什么呢?那我们就要带着这些疑问去探究他们了,在百度上一顿操作之后,我们知道了javac是java自带的编译器,将java语言规范转化成JVM能够识别的字节码文件(.class文件);这是就更加懵了,甚至有点想跳过,一个过来人告诉你,不要这样子哦。下面我们来 总结一下这里面的问题,然后去探究一下java的世界到底是一个怎样的存在。
问题有:
1、 什么是javac编译器?它又有什么作用?它又是怎样工作的呢?
2、 为什么要把字节码文件加载到JVM中呢?字节码文件又是如何加载到JVM中的?
3、 JVM是什么?它是如何工作的呢?
三、解决疑问
看到这些疑问你是不是有点兴奋了,原来java可以学习的东西有这么多的啊,那我们就开始我们的探索之旅吧。问题要一个个的解决,我们就按顺序进行研究吧!
编译原理
1.1 javac是java语言自带的一种编译器,我们都知道java有自己的语言规范,写错了一小处代码,整个项目都会无法运行,这就是规范的魅力,无规矩不成方圆,代码世界中同样如此,每一门语言都是一个王国,我们都是它的臣民;但是java语言不是机器语言,我们的机器是无法识别的,于是我们的java国王大手一挥创建了外交部,让javac负责这个部门,让它负责对外交流,但是javac这个小伙发现一个人的力量是有限的,要充分发挥自己的部长身份,于是他找到自己的得力干将JVM,让他负责与机器王国进行交流;每当国王发布新的命令的时候(.java文件),javac就将其编译成字节码文件,然后直接丢给自己的小弟JVM,自己只负责与国王(java类)对话交流;
1.2 Java的编译环节分为四个步骤,绝大部分的编译器也是这样设计的,四个步骤依次为:
- 词法解析
- 语法解析
- 语义解析
-
生成字节码
编译器的功能就是将java代码翻译成JVM规范下的字节码文件,最重要的点是符合jvm规范,我们的翻译过程也主要是围绕着从一个规范到另一个规范的过程展开;
1.2.1 词法解析:
词法解析的作用就是生成符合java语法规范的Token序列;注释:
Token序列就是一组对应源码字符集合的单词序列,其实上就是一个枚举类型,内部定义了许多符合java语法规范并与源码字符集合相对应的枚举常量;
这时我们肯定会非常好奇,java源码是如何转换成Token序列的呢?java的源码又是如何和已经生成的Token序列保持一个长久的对应关系呢?
词法解析器在将源码翻译成Token序列之前,会先把这个源码字符集合转化为一个Name对象,每一个源码字符集合都是一个Name对象;Keywsords这个类(这里可以把它理解为一个工具类,名字不重要)会把所有的枚举常量转换为一个Name对象,然后将其存储在Name对象的内部类Table中,这时候Name对象就与Token序列建立了一个映射关系;每当我们传进来一个源码字符集合的Name对象的时候,词法解析器就会先找到Name类的内部类Table,在里面获取到对应的Token对象后,将源码字符集合与对应的Token对象的关系存储在Keywords类的Token key[]数组中;我们可以用一张图来表示这个过程,如下:
1.2.2 语法解析:
语法解析的作用就是把匹配得到的Token序列整合成一个语法树;
1.2.3 语义分析:
语义分析就是将语法分析产生的语法树进一步完善,例如给类 添加的默认构造函数,检查变量在使用前是否初始化,将一些常量进行合并处理,检查操作变量类型是否匹配,检查所有的操作语句是否可达,检查异常是否已经捕获或者抛出,解除java语法糖,等。
1.2.4 生成字节码:
遍历语法树,生成最终的字节码。
Java虚拟机(JVM)
1、大家都知道Java是一门可以跨平台的语言,就是因为java源代码的编译结果字节码文件,而字节码文件是在JVM上面运行的,JVM是java技术的真正核心,俗话说得好,一个人对java理解的深度取决于他对JVM的理解深度,由此可以看出,JVM是我们的重中之重;
2、字节码文件加载到JVM中又是经历了一个怎样的过程呢,那我们就读一下下面的这一篇文章吧!
畅谈类加载的过程
3、java王国的国王想知道JVM这个小伙是怎么做的这么优秀的,谁先彻底研究清除,重重有赏,无数谋士纷纷前往,但又有几个真的了解了这个城府如此之深的小伙呢;但我们不能放弃,下面就让我们一起去剖析一下JVM到底是一个什么东西,他是如何做到外交部长交给他的任务的:
绑架JVM