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

HelloWorld.java

  1. public class HelloWorld {
  2. /**
  3. * @param args
  4. */
  5. public static void main(String[] args) {
  6.  
  7. System.out.println("Hello World");
  8. }
  9. }

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

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

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

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

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

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

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

HelloWorld的字节码

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

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

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

javap -classpath . -c HelloWorld

  1. public class HelloWorld extends java.lang.Object{
  2. public HelloWorld();
  3. Code:
  4. 0: aload_0
  5. 1: invokespecial #1; //Method java/lang/Object."<init>":()V
  6. 4: return
  7. public static void main(java.lang.String[]);
  8. Code:
  9. 0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
  10. 3: ldc #3; //String Hello World
  11. 5: invokevirtual #4; //Method
  12. java/io/PrintStream.println:(Ljava/lang/String;)V
  13. 8: return
  14. }

以上代码包含了两个方法,一个是构造方法,由编译器自动插入;另一个是main方法。

在每个方法的下面,都有一系列的指令,如aload_0,invokespecial #1等。每个指定对应的含义可以查看Java字节码指令列表。举个例子,aload_0加载栈中局部变量的引用,getstatic获取类中的静态字段值。注意getstatic后面的"#2",其指向运行时常量池,常量池是Java运行时数据区域。因此,使用"javap -verbose"命令,可以帮助我们查看常量池。

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

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

javap -classpath . -verbose HelloWorld

  1. public class HelloWorld extends java.lang.Object
  2. SourceFile: "HelloWorld.java"
  3. minor version: 0
  4. major version: 50
  5. Constant pool:
  6. const #1 = Method #6.#15; // java/lang/Object."<init>":()V
  7. const #2 = Field #16.#17; // java/lang/System.out:Ljava/io/PrintStream;
  8. const #3 = String #18; // Hello World
  9. const #4 = Method #19.#20; //
  10. java/io/PrintStream.println:(Ljava/lang/String;)V
  11. const #5 = class #21; // HelloWorld
  12. const #6 = class #22; // java/lang/Object
  13. const #7 = Asciz <init>;
  14. const #8 = Asciz ()V;
  15. const #9 = Asciz Code;
  16. const #10 = Asciz LineNumberTable;
  17. const #11 = Asciz main;
  18. const #12 = Asciz ([Ljava/lang/String;)V;
  19. const #13 = Asciz SourceFile;
  20. const #14 = Asciz HelloWorld.java;
  21. const #15 = NameAndType #7:#8;// "<init>":()V
  22. const #16 = class #23; // java/lang/System
  23. const #17 = NameAndType #24:#25;// out:Ljava/io/PrintStream;
  24. const #18 = Asciz Hello World;
  25. const #19 = class #26; // java/io/PrintStream
  26. const #20 = NameAndType #27:#28;// println:(Ljava/lang/String;)V
  27. const #21 = Asciz HelloWorld;
  28. const #22 = Asciz java/lang/Object;
  29. const #23 = Asciz java/lang/System;
  30. const #24 = Asciz out;
  31. const #25 = Asciz Ljava/io/PrintStream;;
  32. const #26 = Asciz java/io/PrintStream;
  33. const #27 = Asciz println;
  34. const #28 = Asciz (Ljava/lang/String;)V;
  35. {
  36. public HelloWorld();
  37. Code:
  38. Stack=1, Locals=1, Args_size=1
  39. 0: aload_0
  40. 1: invokespecial #1; //Method java/lang/Object."<init>":()V
  41. 4: return
  42. LineNumberTable:
  43. line 2: 0
  44. public static void main(java.lang.String[]);
  45. Code:
  46. Stack=2, Locals=1, Args_size=1
  47. 0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
  48. 3: ldc #3; //String Hello World
  49. 5: invokevirtual #4; //Method
  50. java/io/PrintStream.println:(Ljava/lang/String;)V
  51. 8: return
  52. LineNumberTable:
  53. line 9: 0
  54. line 10: 8
  55. }

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. day25类的组合多态封装

    类的组合多态与封装类的组合 1. 什么是组合  组合指的是某一个对象拥有一个属性,该属性的值是另外一个类的对象 2. 为何要用组合  通过为某一个对象添加属性(属性的值是另外一个类的对象)的方式,可以 ...

  2. 零基础学习python_模块(50-52课)

    今天学了下模块,那什么是模块呢?其实我们写的以py结尾的一个文件就是一个模块,模块也就是程序 还记得我们之前学过容器.函数.类吧 容器    ->    数据的封装 函数    ->   ...

  3. 【Git使用】SourceTree可视化工具的安装和使用攻略

    1,下载并安装 sourceTree http://downloads.atlassian.com/software/sourcetree/windows/SourceTreeSetup_1.6.14 ...

  4. 【Java】边框总结

    [Java]边框总结 Table of Contents 1 例子代码与结果 2 javax.swing.border 3 BorderFactory 4 LineBorder 5 MatteBord ...

  5. apache atlas资料收集

    apache atlas  http://atlas.apache.org/ http://blog.csdn.net/ganglia/article/details/51457691

  6. 31.网站数据监控-2(scrapy文件下载)

    温州数据采集 这里采集网站数据是下载pdf:http://wzszjw.wenzhou.gov.cn/col/col1357901/index.html(涉及的问题就是scrapy 文件的下载设置,之 ...

  7. jquery的ajax,请求JSON数据。

    第一个页面:1.htm <!DOCTYPE html> <html> <head> <title></title> <script t ...

  8. 34. Win7_x64安装oracle11g出现DIM-00019

    Win7_x64安装oracle11g出现如下异常处理方式 DIM-00019:创建服务时出错. O/S-ERROR:(OS 1726)远程过程调用失败. 点击弹出的错误框上的确定按钮后,继续弹出错误 ...

  9. springboot retry

    try/catch,while 循环或者定时任务  这样看起来 好  low sping boot  retry , 这样代码更简洁 eg: @Retryable(value= {RemoteAcce ...

  10. Windows下开启Redis PHP拓展

    1. 安装Redis windows下redis的版本,git地址https://github.com/MSOpenTech/redis/releases 2. 打开phpInfo,查看当前PHP是N ...