深入理解hello world
对于每个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/
深入理解hello world的更多相关文章
- 理解CSS视觉格式化
前面的话 CSS视觉格式化这个词可能比较陌生,但说起盒模型可能就恍然大悟了.实际上,盒模型只是CSS视觉格式化的一部分.视觉格式化分为块级和行内两种处理方式.理解视觉格式化,可以确定得到的效果是应 ...
- 彻底理解AC多模式匹配算法
(本文尤其适合遍览网上的讲解而仍百思不得姐的同学) 一.原理 AC自动机首先将模式组记录为Trie字典树的形式,以节点表示不同状态,边上标以字母表中的字符,表示状态的转移.根节点状态记为0状态,表示起 ...
- 理解加密算法(三)——创建CA机构,签发证书并开始TLS通信
接理解加密算法(一)--加密算法分类.理解加密算法(二)--TLS/SSL 1 不安全的TCP通信 普通的TCP通信数据是明文传输的,所以存在数据泄露和被篡改的风险,我们可以写一段测试代码试验一下. ...
- node.js学习(三)简单的node程序&&模块简单使用&&commonJS规范&&深入理解模块原理
一.一个简单的node程序 1.新建一个txt文件 2.修改后缀 修改之后会弹出这个,点击"是" 3.运行test.js 源文件 使用node.js运行之后的. 如果该路径下没有该 ...
- 如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念
一.前言 DDD(领域驱动设计)的一些介绍网上资料很多,这里就不继续描述了.自己使用领域驱动设计摸滚打爬也有2年多的时间,出于对知识的总结和分享,也是对自我理解的一个公开检验,介于博客园这个平 ...
- 学习AOP之透过Spring的Ioc理解Advisor
花了几天时间来学习Spring,突然明白一个问题,就是看书不能让人理解Spring,一方面要结合使用场景,另一方面要阅读源代码,这种方式理解起来事半功倍.那看书有什么用呢?主要还是扩展视野,毕竟书是别 ...
- ThreadLocal简单理解
在java开源项目的代码中看到一个类里ThreadLocal的属性: private static ThreadLocal<Boolean> clientMode = new Thread ...
- JS核心系列:理解 new 的运行机制
和其他高级语言一样 javascript 中也有 new 运算符,我们知道 new 运算符是用来实例化一个类,从而在内存中分配一个实例对象. 但在 javascript 中,万物皆对象,为什么还要通过 ...
- 深入理解JS 执行细节
javascript从定义到执行,JS引擎在实现层做了很多初始化工作,因此在学习JS引擎工作机制之前,我们需要引入几个相关的概念:执行环境栈.全局对象.执行环境.变量对象.活动对象.作用域和作用域链等 ...
- 浅谈我对DDD领域驱动设计的理解
从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决. 比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品 ...
随机推荐
- for循环执行时在,每执行一次for循环中弹出提示框,延时问题
在需求中,ajax的返回值,根据数组内容的长度去做循环,每循环一次弹出提示框,发现for循环的执行速度非常之快,想到了延时,但是在for循环中延时并不能解决这个问题. 查到setTimeout的递归处 ...
- Python——列表深浅拷贝
一.深浅拷贝 如果希望将列表复制一份,通过列表的内置方法copy就可以实现: s = [[1,2],3,4] s1 = s.copy() print(s) print(s1) 拷贝出的列表s1与原列表 ...
- 19.python设置单线程和多线程
1.单线程实例: 代码如下: from time import ctime,sleep def music(A): for i in range(2): print ("I was list ...
- python学习笔记_week21
note 上节内容回顾: 1.请求周期 url> 路由 > 函数或类 > 返回字符串或者模板语言? Form表单提交: 提交 -> url > 函数或类中的方法 - .. ...
- SambaJava API
做一个 backup package net.jnas; import java.io.File; import java.io.FileInputStream; import java.io.Fil ...
- kettle实现简单的增量同步
下载 pdi-ce-7.0.0.0-25.zip 解压 安装jdk 1.7以上的版本 配置环境变量 下载并将mysql-connector-java-5.1.39.jar 拷贝到 \data-inte ...
- tensorflow实战系列(三)一个完整的例子
#!/usr/bin/env python2# -*- coding: utf-8 -*-"""Created on Wed Jan 18 08:42:55 2017 @ ...
- APP-8.1-百度语音应用
1.百度语音登录地址 http://yuyin.baidu.com/ 2.控制台创建应用 3.生成签名 3.1Postman软件应用 APP-8.2-Postman应用 3.2Postman执行 UR ...
- jquery下插入标签以及clone的应用
//内部插入 插入一个儿子 //var $ele = $("<h1></h1>")//创建h1标签 // $ele.html('hello') // $el ...
- Bash:精华
# 声明索引数组(以从0开始的整数做索引的数组).以下三种等效. declare -a array declare array=(this is numeric array ) array=(this ...