• 深入理解Java HelloWorld


    HelloWorld是每个Java程序员都知道的程序。它很简单,但是简单的开始可以引导你去深入了解更复杂的东西。这篇文章将探究从这个HelloWorld这个简单程序中可以学到的东西。如果你对HelloWorld有独到的理解,欢迎留下你的评论。

      HelloWorld.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class HelloWorld {
     
        /**
         *
         * @param args
         */
     
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            System.out.println("Hello World");
        }
     
    }

     1、为什么一切都是从类开始?

      Java程序是从类开始构建的, 每个方法和字段都必须在类里面。这是由于Java面向对象的特性: 一切都是对象,它是类的一个实例。面向对象编程语言相比函数式编程语言有许多的优势,比如更好的模块化、可扩展性等等。

     2、为什么总有一个“main方法”?

      main方法是程序的入口,并且是静态方法。static关键字意味着这个方法是类的一部分,而不是实例对象的一部分。为什么会这样呢? 为什么我们不用一个非静态的方法作为程序的入口呢?

      如果一个方法不是静态的,那么对象需要先被创建好以后才能使用这个方法。因为这个方法必须要在一个对象上调用。对于一个入口来说,这是不现实的。因此,程序的入口方法是静态的。

      参数 “String[] args”表明可以将一个字符串数组传递给程序来帮助程序初始化。

     3、HelloWorld程序的字节码

      为了执行这个程序,Java文件首先被编译成Java字节码存储到.class文件中。那么字节码看起来是什么样的呢?字节码本身是不可读的,如果我们使用一个二进制编辑器打开,它看起来就像下面那样:

      在上面的字节码中,我们可以看到很多的操作码(比如CA、4C等等),它们中的每一个都有一个对应的助记码(比如下面例子中的aload_0)。操作码是不可读的,但是可以使用javap来查看.class文件的助记形式。

      对于类中的每个方法执行“javap -c”可以输出反汇编代码。反汇编代码即组成Java字节码的指令。

    1
    javap -classpath .  -c HelloWorld
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Compiled from "HelloWorld.java"
    public class HelloWorld extends java.lang.Object{
    public HelloWorld();
      Code:
       0:   aload_0
       1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
       4:   return
      
    public static void main(java.lang.String[]);
      Code:
       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
    }

      上面的代码包含两个方法: 一个是编译器推断出来的默认的构造器;另外一个是main方法。

      接下来,每个方法都有一系列的指令。比如aload_0、invokespecial #1等等。可以在Java字节码指令集中查到每个指令的功能,例如aload_0用来从局部变量0中加载一个引用到堆栈,getstatic用来获取类的一个静态字段值。可以注意到,getstatic指令之后的“#2″指向的是运行期常量池。常量池是JVM运行时数据区之一。我们可以通过“javap -verbose”命令来查看常量池。

      另外, 每个指令从一个数字开始,比如0、1、4等等。在.class文件中,每个方法都有一个对应的字节码数组。这些数字对应于存储每个操作码及其参数的数组的下标。每个操作码都是1个字节长度,并且指令可以有0个或多个参数。这就是为什么这些数字不是连续的原因。

      现在,我们使用“javap -verbose”这个命令来进一步观察这个类。

    1
    javap -classpath . -verbose HelloWorld
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    Compiled from "HelloWorld.java"
    public class HelloWorld extends java.lang.Object
      SourceFile: "HelloWorld.java"
      minor version: 0
      major version: 50
      Constant pool:
    const #1 = Method   #6.#15; //  java/lang/Object."<init>":()V
    const #2 = Field    #16.#17;    //  java/lang/System.out:Ljava/io/PrintStream;
    const #3 = String   #18;    //  Hello World
    const #4 = Method   #19.#20;    //  java/io/PrintStream.println:(Ljava/lang/String;)V
    const #5 = class    #21;    //  HelloWorld
    const #6 = class    #22;    //  java/lang/Object
    const #7 = Asciz    <init>;
    const #8 = Asciz    ()V;
    const #9 = Asciz    Code;
    const #10 = Asciz   LineNumberTable;
    const #11 = Asciz   main;
    const #12 = Asciz   ([Ljava/lang/String;)V;
    const #13 = Asciz   SourceFile;
    const #14 = Asciz   HelloWorld.java;
    const #15 = NameAndType #7:#8;//  "<init>":()V
    const #16 = class   #23;    //  java/lang/System
    const #17 = NameAndType #24:#25;//  out:Ljava/io/PrintStream;
    const #18 = Asciz   Hello World;
    const #19 = class   #26;    //  java/io/PrintStream
    const #20 = NameAndType #27:#28;//  println:(Ljava/lang/String;)V
    const #21 = Asciz   HelloWorld;
    const #22 = Asciz   java/lang/Object;
    const #23 = Asciz   java/lang/System;
    const #24 = Asciz   out;
    const #25 = Asciz   Ljava/io/PrintStream;;
    const #26 = Asciz   java/io/PrintStream;
    const #27 = Asciz   println;
    const #28 = Asciz   (Ljava/lang/String;)V;
      
    {
    public HelloWorld();
      Code:
       Stack=1, Locals=1, Args_size=1
       0:   aload_0
       1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
       4:   return
      LineNumberTable:
       line 2: 0
      
      
    public static void main(java.lang.String[]);
      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 9: 0
       line 10: 8
    }

      引用JVM规范中的描述:运行期常量池提供的功能类似传统编程语言的符号表所起作用, 尽管它比传统的符号表包含的内容更广范。

      “invokespecial #1″指令中的“#1″指向常量池中的#1常量。这个常量是 “Method #6.#15;”。通过这个数字,我们可以递归地得到最终的常量。

      LineNumberTable为调试器提供了用来指示Java源代码与字节码指令之间的对应信息。例如,Java源代码中的第9行对应于main方法中的字节码0,并且第10行对应于字节码8。

      如果想要了解更多关于字节码的内容,可以创建一个更加复杂的类进行编译和查看,HelloWorld真的只是一个开始。

     4、HelloWorld在JVM中是如何执行的?

      现在的问题是JVM是怎样加载这个类并调用main方法?

      在main方法执行之前, JVM需要加载链接以及初始化这个类。

      1. 加载将类/接口的二进制形式装入JVM中。

      2. 链接将二进制类型的数据融入到JVM的运行时。链接由3个步骤组成:验证、准备、以及解析(可选)。验证确保类、接口在结构上是正确的;准备涉及到为类、接口分配所需要的内存;解析是解析符号引用。

      3. 最后,初始化为类变量分配正确的初始值。

      加载工作是由Java类加载器来完成的。当JVM启动时,会使用下面三个类加载器:

    1. Bootstrap类加载器:加载位于/jre/lib目录下的核心Java类库。它是JVM核心的一部分,并且使用本地代码编写。
    2. 扩展类加载器:加载扩展目录中的代码(比如/jar/lib/ext)。
    3. 系统类加载器:加载在CLASSPATH上的代码。

      所以,HelloWorld类是由系统加载器加载的。当main方法执行时,它会触发加载其它依赖的类,进行链接和初始化。前提是它们已经存在。

      最后,main()帧被压入JVM堆栈,并且程序计数器(PC)也进行了相应的设置。然后,PC指示将println()帧压入JVM堆栈栈顶。当main()方法执行完毕会被弹出堆栈,至此执行过程就结束了。

     参考文档

    1. 加载
    2. 类加载机制
    3. 类加载器

      原文链接: programcreek 翻译: ImportNew.com - 黄飞飞

    http://www.admin10000.com/document/3828.html

  • 相关阅读:
    PHP通过日志来发现问题
    php环境重启
    排行榜的实现
    git相关使用技巧和问题
    lua State加载部分库
    c++ 解析json
    查看某个进程允许打开的最大文件描述符
    glog安装与使用
    ubuntu update-alternatives
    gcc安装多个版本
  • 原文地址:https://www.cnblogs.com/zhaofei/p/3539497.html
Copyright © 2020-2023  润新知