对于每个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/

深入理解“HelloWorld”小程序的更多相关文章

  1. 实现一个servlet的helloworld小程序(不适用Eclipse)

    实现一个servlet的helloworld小程序(不适用Eclipse) 1. 在tomcat中的webapps下建一个应用程序FirstServlet(C:\tomcat\webapps\Firs ...

  2. 理解微信小程序的双线程模型

    有过微信小程序开发经验的朋友应该都知道"双线程模型"这个概念,本文简单梳理一下双线程模型的一些科普知识,学识浅薄,若有错误欢迎指正. 我以前就职于「小程序·云开发」团队,在对外的一 ...

  3. 【原创】1、简单理解微信小程序

    先看下网站的运行方式: 而小程序是这样: what?就这样?是的,就这样.那小程序官方提供的Wafer,还有Wafer2...想太多了,抛弃它们吧.不应当为了解决一个简单的旧问题而去整一个复杂的新问题 ...

  4. 教你理解微信小程序的生命周期和运行原理

    转自:http://blog.csdn.net/tsr106/article/details/53052879  写微信小程序,他的生命周期不能不知道,不知道小程序就会出现各种bug而无法解决.小助君 ...

  5. 理解微信小程序的生命周期和运行原理

    写微信小程序,他的生命周期不能不知道,不知道小程序就会出现各种bug而无法解决.小助君公众号带你学习小程序的生命周期和运行原理. 小程序由两大线程组成:负责界面的线程(view thread)和服务线 ...

  6. 深度理解微信小程序的思想

    这篇文章不涉及小程序技术方面得问题,只讨论小程序的核心问题. 探讨一个问题最好的方法是问"为什么",这篇文章主要思路是通过回答以下几个问题来探讨微信小程序的"思想&quo ...

  7. 理解微信小程序Wepy框架的三个事件交互$broadcast,$emit,$invoke

    $broadcast: $broadcast事件是由父组件发起,所有子组件都会收到此广播事件,除非事件被手动取消.事件广播的顺序为广度优先搜索顺序,如上图,如果页面Page_Index发起一个$bro ...

  8. 微信小程序的应用及信息整合,都放到这里了

    微信小程序终于开始公测了,这篇文章也终于可以发布了. 这篇文章可以说是微信小程序系列三部曲最后一篇.8 月份,小程序推出前,我写了<别开发 app 了>详细阐述了为什么创业应该放弃原生 a ...

  9. 【腾讯Bugly干货分享】微信小程序开发思考总结——腾讯“信用卡还款”项目实践

    本文来自于腾讯bugly开发者社区,未经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/58212d0fa7a7574c4f4cc3c5 作者:peggy 小程序概述 1 ...

随机推荐

  1. 关于EF的 序列化类型为“XXX”的对象时检测到循环引用。

    在用Ef的时候,也许经常会遇到循环引用的错误. 下面提供解决办法.(不是Json.Net,如果是Json.Net可以给导航属性通过增加特性标签来解决该问题) ef大多数问题,可以通过ToList()来 ...

  2. Scalaz(22)- 泛函编程思维: Coerce Monadic Thinking

    马上进入新的一年2016了,来点轻松点的内容吧.前面写过一篇关于用Reader实现依赖注入管理的博文(Scalaz(16)- Monad:依赖注入-Dependency Injection By Re ...

  3. php高版本不再使用mysql_connect()来连接数据库

    想用php生成一个mysql数据字典导出来,用到下面代码会 $mysql_conn = mysql_connect ( "$dbserver", "$dbusername ...

  4. Windows台的FailOver群集简介

    首先,您需要有一些服务器硬件方面知识. 我们介绍Windows平台的FailOver群集,以多个站点场景为例,如下图示: 八个结点NODE,Windows的FailOver群集,依赖SAN存储同步各个 ...

  5. wso2esb之代理服务 Proxy Services

    代理服务 顾名思义,代理服务充当了WSO2 ESB服务的代理,通常是一个已经存在的服务端点,代理服务可以使用不同的传输方式. 客户可以直接发送请求代理服务的ESB,客户看到服务代理. 运行示例 配置W ...

  6. Code First :使用Entity. Framework编程(3) ----转发 收藏

    第三章 对属性使用约定和配置 在第2章,对Code First的约定以及如何通过配置覆写默认约定行为进行了大致的介绍.学习了如何使用Data Annotations进行配置,也学习了如何使用Fluen ...

  7. 一些ajax代码

    $.ajax({ type : "get", url : "list_hot_ajax.json", data : {"provinceId" ...

  8. WPF如何实现一个漂亮的页签导航UI

    最近看到一个比较漂亮的UI主界面,该UI是用左边的页签进行导航,比较有特色,就想着尝试用WPF来实现一下.经过一番尝试,基本上将UI设计图的效果用WPF程序进行了实现.下面介绍一下主要的思路: 1 U ...

  9. 网页引用本地电脑的字体 css设置浏览器会不显示的解决办法

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  10. CSS3滚动条-webkit-scrollbar

    webkit现在支持拥有overflow属性的区域,列表框,下拉菜单,textarea的滚动条自定义样式. 如果你想跳过介绍,直接看demo的话,请点击demo 滚动条是一个伪元素,可以自定义样式.这 ...