Java百问之一——从java HelloWorld中我们能学到什么?

Java百问之一——从java HelloWorld中我们能学到什么?

-------------------------------------------------------我是分割线------------------------------------------  
在学习Java程序设计的过程中偶然发现网络上哪位大牛总结的Java百问(http://www.programcreek.com/2013/04/java%E7%99%BE%E9%97%AE/),可惜部分文章是英文的。正好出于学习的需要,闲暇时间拿来翻译翻译。本篇文章原文在(http://www.programcreek.com/2013/04/what-can-you-learn-from-a-java-helloworld-program/)。
-------------------------------------------------------我是分割线------------------------------------------
    
    下面这个打印HelloWorld的程序相信所有的java程序员都知道。它非常简单,但是对简单事物刨根问底能够让你了解更复杂的机制。所以在这篇文章中,我将深入探究这个简单的程序,看看我们能从中学到什么。如果你觉得这篇文章对你的学习有帮助,欢迎评论。

public class HelloWorld {

    /**

     * @param args

     */

    public static void main(String[] args) {

        // TODO Auto-generated method stub

        System.out.println("Hello World");

    }

}

 

1、为什么所有的东西都是从class开始?
    java程序是从类(class)开始构建的,所有的方法(method)和域(field)都必须在class中。这是因为java的面向对象特性:每一个对象都是类的实例化。相对于面向过程的编程语言,面向对象语言具有模块化、可扩展等一系列优势。

2、为什么总有个“main”方法?
    “main”方法是程序的入口,它是静态的(static)。“static”意味着这个方法是类的一部分,而不是对象的一部分。
    
    为什么是这样?为什么我们不用一个非静态的(non-static)的方法作为程序的入口?
    
    如果一个方法不是静态的,那么对象就需要先构建这个方法,因为方法必须被对象调用。作为程序入口,这不符合逻辑。因此,程序的入口方法必须是静态的。

    参数"String args[]"表示一个String数组可以传递给程序,用以帮助完成程序的初始化。

3、HelloWorld的字节码
    执行程序,java文件会首先编译成java字节码并存储在.class文件中。

    字节码长什么样呢?

    字节码不具可读性,但是我们可以用hex editor(十六进制文件查看器)查看,字节码长成这个样子:



    在上面的字节码中我们能够看到许多的操作码(opcode)(例如CA,4C,等等),每一个操作码都有一个相应的助记码(mnemonic  code)(例如在接下来示例中的aload_0)。操作码并不可读,但是我们能够用javap查看一个.class文件的助记码形式。

    “javap -c”打印类中所有方法的反汇编代码。反汇编代码表示java字节码中包含的指令。

javap -classpath . -c HelloWorld

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

}


    上面的代码包含两个方法:一个是默认构造器,这个是编译器自动生成的;另外一个是主函数。

    在每一个方法的下面都包含一系列的指令,例如aload_0,invokespecial #1,等等。每条指令都有什么功能可以查看Java bytecode instruction listings。举个例子,aload_0将位置为0的引用压入栈,getstatic读取类中一个静态域的值。注意在getstatic指令后的“#2”指向运行时的常量池(run-time constant pool)。常量池是JVM运行时数据区域(JVM run-time data areas)中的一个。让我们看看常量池吧,使用命令“javap -verbose”就可以。

    总的来说,每一条指令都是从一个数字开始,就像0,1,4,等等。在.class文件中,每一个方法对应一个字节码数组,数组中存储着操作码和它的参数,而这些数字对应到这个数组的索引。每一条操作码都是1byte长,而这些指令可以有0到多个参数。这也是这些数字不连贯的原因。

    我们可以用“javap -verbose”进一步查看类。

javap -classpath . -verbose HelloWorld

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源代码对应字节码指令的行号信息。例如,在示例程序中主方法的第9行对应到字节码中的0行,第10行对应字节码的第8行。

    如果你想知道更多的关于字节码的知识,你可以创建一个更加复杂的class文件。Helloword仅仅只是个开始。

4、程序在JVM中是如何执行的?
    现在的问题是:JVM如何载入class并且调用主方法的?
    
    在主方法执行之前,JVM需要做三件事情:1)load,2)link,3)initialize the class。
    1)Loading表示将二进制格式的class/interface文件载入到JVM
    2)Linking将二进制类型的数据嵌入JVM的运行时状态
       Linking又包括三个步骤:验证、准备和解析。验证-保证类/接口在结构上正确;准备-分配类/接口需要的内存;解析-检查这个类/接口的引用。
    3)Initialization阶段初始化正确的值

    这些工作都是由java ClassLoader完成。当JVM启动时,有三个ClassLoader会被使用:
    1、Bootstrap class loader:载入位于/jre/lib directory的核心java库。这是JVM核心的一部分,由本机代码实现;
    2、Extensions class loader:载入扩展目录下的代码(例如/jar/lib/ext);
    3、System class loader:载入在CLASSPATH路径下发现的代码。

    所以HelloWorld类是由System class loader载入。当主方法执行时,它会触发loading, linking, and initialization of other dependent classes,如果这些类都存在的话。

    最后,main()栈帧被push到JVM的栈中,指令计数器(PC)也进行了相应的设置。接下来,PC将会指示println()的栈帧入栈。当main()方法结束后,主方法将会出栈,程序运行结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值