对于每个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. 常用的一些SQL语句整理,也许有你想要的。

    本篇文章是对一些常用的sql语句进行了总结与分析,需要的朋友参考下,也许会有你需要的. 1.SQL行列转换 问题:假设有张学生成绩表(tb)如下:姓名 课程 分数张三 语文 74张三 数学 83张三 ...

  2. 仿Office的程序载入窗体

    初次接触启动界面记不清是在哪一年了,估计是小学四年级第一次打开Office Word的时候吧,更记不清楚当时的启动界面是长啥样了.后来随着使用的软件越来越多,也见到各式各样的启动界面.下面就列举了两个 ...

  3. EF更新,数据库值变化,前台页面并不变化,刷新也不变化,重新运行程序则变化----开发中遇到的问题(已解决)

    首先说一下我遇到这个情况的代码情景,首先上错误代码 UserInfo userInfo = Session["UserInfo"] as UserInfo; ); 这段代码所呈现的 ...

  4. 对于一些Http远程连接Api安全的看法;

    文章来源于 :http://lesg.cn/?p=122 我的个人博客站点 对于一些Http远程连接Api安全的看法: 当不同系统需要互相通信的时候:如果无法用webservice等方式链接的时候另一 ...

  5. 不可或缺 Windows Native (23) - C++: 虚函数

    [源码下载] 不可或缺 Windows Native (23) - C++: 虚函数 作者:webabcd 介绍不可或缺 Windows Native 之 C++ 虚函数 示例1.基类CppHuman ...

  6. $("").click与onclick的区别示例介绍

    Html代码: <script type="text/javascript"> $(function(){ $("#btn4").click(fun ...

  7. Scalaz(46)- scalaz-stream 基础介绍

    scalaz-stream是一个泛函数据流配件库(functional stream combinator library),特别适用于函数式编程.scalar-stream是由一个以上各种状态的Pr ...

  8. 利用Redis cache优化app查询速度实践

    注意:本篇文章译自speeding up existing app with a redis cache,如需要转载请注明出处. 发现问题 在应用解决方法之前,我们需要对我们面对的问题有一个清晰的认识 ...

  9. 搭建自己的ngrok服务(国内直接可用http://qydev.com/#)

    ngrok 服务可以分配给你一个域名让你本地的web项目提供给外网访问, 特别适合向别人展示你本机的web demo 以及调试一些远程的API (比如微信公众号,企业号的开发) ngrok的官方服务可 ...

  10. jQuery超酷下拉插件6种效果演示

    原始的下拉框很丑啦, 给大家一款jQuery超酷下拉插件6种效果 效果预览 下载地址 实例代码 <div class="container"> <section ...