对于每个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      转载请注明出处,谢谢~ 

深入理解hello world的更多相关文章

  1. 理解CSS视觉格式化

    前面的话   CSS视觉格式化这个词可能比较陌生,但说起盒模型可能就恍然大悟了.实际上,盒模型只是CSS视觉格式化的一部分.视觉格式化分为块级和行内两种处理方式.理解视觉格式化,可以确定得到的效果是应 ...

  2. 彻底理解AC多模式匹配算法

    (本文尤其适合遍览网上的讲解而仍百思不得姐的同学) 一.原理 AC自动机首先将模式组记录为Trie字典树的形式,以节点表示不同状态,边上标以字母表中的字符,表示状态的转移.根节点状态记为0状态,表示起 ...

  3. 理解加密算法(三)——创建CA机构,签发证书并开始TLS通信

    接理解加密算法(一)--加密算法分类.理解加密算法(二)--TLS/SSL 1 不安全的TCP通信 普通的TCP通信数据是明文传输的,所以存在数据泄露和被篡改的风险,我们可以写一段测试代码试验一下. ...

  4. node.js学习(三)简单的node程序&&模块简单使用&&commonJS规范&&深入理解模块原理

    一.一个简单的node程序 1.新建一个txt文件 2.修改后缀 修改之后会弹出这个,点击"是" 3.运行test.js 源文件 使用node.js运行之后的. 如果该路径下没有该 ...

  5. 如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念

    一.前言     DDD(领域驱动设计)的一些介绍网上资料很多,这里就不继续描述了.自己使用领域驱动设计摸滚打爬也有2年多的时间,出于对知识的总结和分享,也是对自我理解的一个公开检验,介于博客园这个平 ...

  6. 学习AOP之透过Spring的Ioc理解Advisor

    花了几天时间来学习Spring,突然明白一个问题,就是看书不能让人理解Spring,一方面要结合使用场景,另一方面要阅读源代码,这种方式理解起来事半功倍.那看书有什么用呢?主要还是扩展视野,毕竟书是别 ...

  7. ThreadLocal简单理解

    在java开源项目的代码中看到一个类里ThreadLocal的属性: private static ThreadLocal<Boolean> clientMode = new Thread ...

  8. JS核心系列:理解 new 的运行机制

    和其他高级语言一样 javascript 中也有 new 运算符,我们知道 new 运算符是用来实例化一个类,从而在内存中分配一个实例对象. 但在 javascript 中,万物皆对象,为什么还要通过 ...

  9. 深入理解JS 执行细节

    javascript从定义到执行,JS引擎在实现层做了很多初始化工作,因此在学习JS引擎工作机制之前,我们需要引入几个相关的概念:执行环境栈.全局对象.执行环境.变量对象.活动对象.作用域和作用域链等 ...

  10. 浅谈我对DDD领域驱动设计的理解

    从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决. 比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品 ...

随机推荐

  1. redis中key过期事件

    刚到新公司一个月左右,有个新需求,想做定时任务,比如在用户注册时间的3天后推送用户一条消息. 从刚开始脑子里面闪现的数据库轮询,立马否定掉(浪费资源),再到linux系统的定时任务,但是当用户量过大时 ...

  2. mac中svn服务器的搭建以及如何在eclipse中使用

    mac自带了svn客户端和服务端功能. 1.查看svn版本 svnserve --version yintingtingdeMacBook-Pro:~ yintingting$ svnserve -- ...

  3. python-类里面各种方法的学习补充

    #-*- coding:utf-8 -*- #本次学习:类里面各种方法的学习 class SeniorTestingEngineer: work_year=3 salary=15000 def cod ...

  4. StanFord ML 笔记 第六部分&&第七部分

    第六部分内容: 1.偏差/方差(Bias/variance) 2.经验风险最小化(Empirical Risk Minization,ERM) 3.联合界(Union bound) 4.一致收敛(Un ...

  5. SLD Related Gateway Serivces Unavaliable

    SAP NW 7.4 default switched on the ACL (access control list) in gateway service, so only local acces ...

  6. 41.纯 CSS 绘制一支栩栩如生的铅笔

    原文地址: https://segmentfault.com/a/1190000015153865 感想: 不难 HTML code: <div class="pencil" ...

  7. RecyclerView下拉刷新上拉加载更多

    现在Android里都建议用RecyclerView代替ListView和GridView,所以下拉刷新和上拉加载更多也需要实现.下拉刷新可以用SwipeRefreshLayout 包裹Recycle ...

  8. Flex 画图

    <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="h ...

  9. idea 一款神一样的编辑器 IDEA,破解方式

    功能1,可以开发后端如 JAVA, PHP, PYTHON ,NODE 功能2,可以开发前端如 HTML +  CSS + JS 破解方式 1,编辑C:\Windows\System32\driver ...

  10. [Python]实践:实现探测Web服务质量

    来源:Python 自动化运维 技术与最佳实践 HTTP服务是最流行的互联网应用之一,服务质量的好坏关系到用户体验以及网站的运营服务水平,最常用的有两个标准:1.服务的可用性,比是否处于正常提供服务状 ...