英文原文链接,译文链接,原文作者:James Bloom,译者:有孚
明白Java代码是如何编译成字节码并在JVM上运行的非常重要,这有助于理解程序运行的时候究竟发生了些什么。理解这点不仅能搞清语言特性是如何实现的,并且在做方案讨论的时候能清楚相应的副作用及权衡利弊。
本文介绍了Java代码是如何编译成字节码并在JVM上执行的。想了解JVM的内部结构以及字节码运行时用到的各个内存区域,可以看下我前面的一篇关于JVM内部细节的文章。
本文分为三部分,每一部分都分成几个小节。每个小节都可以单独阅读,不过由于一些概念是逐步建立起来的,如果你依次阅读完所有章节会更简单一些。每一节都会覆盖到Java代码中的不同结构,并详细介绍了它们是如何编译并执行的。
1. 第一部分, 基础概念
变量
局部变量
JVM是一个基于栈的架构。方法执行的时候(包括main方法),在栈上会分配一个新的帧,这个栈帧包含一组局部变量。这组局部变量包含了方法运行过程中用到的所有变量,包括this引用,所有的方法参数,以及其它局部定义的变量。对于类方法(也就是static方法)来说,方法参数是从第0个位置开始的,而对于实例方法来说,第0个位置上的变量是this指针。
局部变量可以是以下这些类型:
* char
* long
* short
* int
* float
* double
* 引用
* 返回地址
除了long和double类型外,每个变量都只占局部变量区中的一个变量槽(slot),而long及double会占用两个连续的变量槽,因为这些类型是64位的。
当一个新的变量创建的时候,操作数栈(operand stack)会用来存储这个新变量的值。然后这个变量会存储到局部变量区中对应的位置上。如果这个变量不是基础类型的话,本地变量槽上存的就只是一个引用。这个引用指向堆的里一个对象。
比如:
int i = 5;
编译后就成了
0: bipush 5 2: istore_0
bipush | 用来将一个字节作为整型数字压入操作数栈中,在这里5就会被压入操作数栈上。 |
istore_0 |
这是istore_这组指令集(译注:严格来说,这个应该叫做操作码,opcode ,指令是指操作码加上对应的操作数,oprand。 不过操作码一般作为指令的助记符,这里统称为指令)中的一条,这组指令是将一个整型数字存储到本地变量中。 n代表的是局部变量区中的位置,并且只能是0,1,2,3。再多的话只能用另一条指令istore了,这条指令会接受一个操作数,对应的是局部变量区中的位置信息。 |