• 深入理解hello world


    对于每个Java程序员来说,HelloWorld是一个再熟悉不过的程序。它很简单,但是这段简单的代码能指引我们去深入理解一些复杂的概念。这篇文章,我将探索我们能从这段简单的代码中学到什么。如果你对HelloWorld有独到的理解,请留下你的评论。

    HelloWorld.java

    复制代码
    public class HelloWorld {
        /**
         * @param args
         */
        public static void main(String[] args) {
    
            System.out.println("Hello World");
        }
    }
    复制代码

    为什么所有东西都是从类开始的

    Java程序是基于类构建的,每一个方法,字段必须存在于类里面。这是因为Java是面向对象的:一切都是对象,即一个类的实例。相对于函数式编程,面向对象编程有很多优势,如更加模块化,可扩展性更好等。

    为什么总是需要有一个“main”方法

    main方法是静态方法,程序的入口;静态方法意味着这个方法是属于类,而不是对象。

    那为什么是这样呢?为什么不使用非静态方法作为程序的入口呢?

    如果这个方法是非静态的,那么在使用这个方法之前需要先创建对象,因为非静态方法需要由对象来调用。作为一个程序的入口,这样的设计是不现实的。在没有鸡的情况下,我们不能获取鸡蛋。因此,程序入口被设置为静态方法。

    另外,main方法的入参"String[] args"表明一个字符串数组可以传入该方法用于执行程序的初始化工作。

    HelloWorld的字节码

    为了运行这个程序,Java文件首先被编译成字节码存入一个.class文件。那么这个字节码文件是怎样的呢?字节码本身是不易读的,我们使用十六进制编辑器打开它,结果如下:

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

    "javap -c"可以打印类中每个方法的反汇编代码,反汇编代码即一些指令,这些指定组成了java的字节码。

    javap -classpath . -c HelloWorld

    复制代码
    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加载栈中局部变量的引用,getstatic获取类中的静态字段值。注意getstatic后面的"#2",其指向运行时常量池,常量池是Java运行时数据区域。因此,使用"javap -verbose"命令,可以帮助我们查看常量池。

    另外,每条指令的前面都有一个数字,如0,1,4等。在字节码文件中,每一个方法都有对应的字节码数组。这些数字对应的正是数组的索引,这些数组存放了操作码和对应参数。每个操作码长度为一个字节,可以有0或多个参数,这就是为什么这些数字不是连续的。

    现在,我们可以使用"javap -verbose"命令深入看下这个类:

    javap -classpath . -verbose HelloWorld

    复制代码
    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;",根据这些数字,我们可以递归的得到最终常量。行号表可以方便调试人员知道Java源代码中的哪些行对应字节码中的哪些指令。如,Java源代码中的第9行对应main方法中的code 0,第10行对应code 8。

    如果你想要知道更多关于字节码的内容,可以尝试创建一个更加复杂的类,并编译查看。相对而言,HelloWorld太简单了。

    HelloWorld在JVM中是如何运行的

    现在的问题是JVM如何装载Java类以及如何调用main方法?

    在main方法执行之前,JVM需要完成以下步骤,

    • 装载:装载类或接口的字节码到JVM中
    • 链接:将Java类的二进制代码合并到JVM的运行状态之中的进程,包含3个步骤(验证:确保类或接口结构正确、准备:涉及内存分配相关、解析:解决符号引用)
    • 初始化:为类的变量初始化合适的值;

    加载步骤是由Java类加载器完成的,在JVM启动的时候,使用了三个类加载器:

    • 引导类加载器:加载/jre/lib下的Java核心类库,这些类是Java的核心,使用本地代码编写。
    • 扩展类加载器:加载扩展目录下的代码(如/jar/lib/ext目录)
    • 系统类加载器:加载CLASSPATH下的代码

    所以HelloWorld是由系统类加载器加载的,当main方法执行之前,会触发加载,链接,初始化其它依赖类操作。

    最终,main方法帧 被push到JVM栈中,程序计数器开始做相应操作,将println方法帧push到JVM栈中,当main方法执行完毕,栈中对应的数据被弹出,然后执行完毕。

    译文链接:http://www.programcreek.com/2013/04/what-can-you-learn-from-a-java-helloworld-program/


    @Author      风一样的码农
    @HomePageUrl http://www.cnblogs.com/chenpi/ 
    @Copyright      转载请注明出处,谢谢~ 
  • 相关阅读:
    Java Application Development(包括如何设置本地库)
    谈谈基于Kerberos的Windows Network Authentication
    在OpenSSL中添加自定义加密算法
    单点登录(SSO)的核心--kerberos身份认证协议技术参考(一)
    WindowsXP 系统登陆原理及其验证机制概述
    单点登录(SSO)的核心--kerberos身份认证协议技术参考(二)
    单点登录(SSO)的核心--kerberos身份认证协议技术参考(三)
    windows登录过程 winlogon/gina/Kerberos/kdc
    [C++]static全局变量/全局变量,static函数/普通函数,函数中static变量/函数中的变量,类中的static成员变量/普通类成员变量
    郑重推荐一款软件: http tunnel
  • 原文地址:https://www.cnblogs.com/dzhou/p/9553320.html
Copyright © 2020-2023  润新知