Java字节码运行浅析
来源:互联网 发布:淘宝店铺首页有个红包 编辑:程序博客网 时间:2024/06/14 15:21
转载于:http://it.deepinmind.com/jvm/2014/04/03/java-code-to-byte-code.html
明白Java代码是如何编译成字节码并在JVM上运行的非常重要,这有助于理解程序运行的时候到底发生了些什么。理解这点不仅能搞清语言特性是如何实现的,并且在做方案讨论的时候能知道相应的副作用及权衡利弊。
本文介绍了Java代码是如何编译成字节码并在JVM上执行的。想了解JVM的内部结构以及字节码运行时用到的各个内存区域,可以看下我前面的一篇关于JVM内部细节的文章。
本文分为三部分,每一部分都分成几个小节。每个小节都可以单独阅读,不过由于一些概念是逐步建立起来的,如果你依次阅读完所有章节会更简单一些。每一节都会覆盖到Java代码中的不同结构,并详细介绍了它们是如何编译成字节码并执行的。
- 第一部分, 基础概念
变量
局部变量
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 52: istore_0
当这条指令执行的时候,内存布局是这样的:
class文件中的每一个方法都会包含一个局部变量表,如果这段代码在一个方法里面的话,你在类文件的局部变量表中会找到如下的一条记录。
LocalVariableTable: Start Length Slot Name Signature 0 1 1 i I
字段
Java类里面的字段是作为类对象实例的一部分,存储在堆里面的(类变量对应存储在类对象里面)。关于字段的信息会添加到类文件里的field_info数组里,像下面这样:
ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info contant_pool[constant_pool_count – 1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count];}
另外,如果变量被初始化了,那么初始化的字节码会加到构造方法里。
下面这段代码编译了之后:
public class SimpleClass { public int simpleField = 100;}
如果你用javap进行反编译,这个被添加到了field_info数组里的字段会多出一段描述信息。
public int simpleField; Signature: I flags: ACC_PUBLIC
初始化变量的字节码会被加到构造方法里,像下面这样:
public SimpleClass(); Signature: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: bipush 100 7: putfield #2 // Field simpleField:I 10: return
上述代码执行的时候内存里面是这样的:
这里的putfield指令的操作数引用的是常量池里的第二个位置。JVM会为每种类型维护一个常量池,运行时的数据结构有点类似一个符号表,尽管它包含的信息更多。Java中的字节码操作需要数据,但通常这些数据都太大了,存储在字节码里不适合,它们会被存储在常量池里面,而字节码包含一个常量池里的引用 。当类文件生成的时候,其中的一块就是常量池:
Constant pool: #1 = Methodref #4.#16 // java/lang/Object."<init>":()V #2 = Fieldref #3.#17 // SimpleClass.simpleField:I #3 = Class #13 // SimpleClass #4 = Class #19 // java/lang/Object #5 = Utf8 simpleField #6 = Utf8 I #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 SimpleClass #14 = Utf8 SourceFile #15 = Utf8 SimpleClass.java #16 = NameAndType #7:#8 // "<init>":()V #17 = NameAndType #5:#6 // simpleField:I #18 = Utf8 LSimpleClass; #19 = Utf8 java/lang/Object
常量字段(类常量)
带有final标记的常量字段在class文件里会被标记成ACC_FINAL.
比如
public class SimpleClass { public final int simpleField = 100;}
字段的描述信息会标记成ACC_FINAL:
public static final int simpleField = 100; Signature: I flags: ACC_PUBLIC, ACC_FINAL ConstantValue: int 100
对应的初始化代码并不变:
4: aload_05: bipush 1007: putfield #2 // Field simpleField:I
静态变量
带有static修饰符的静态变量则会被标记成ACC_STATIC:
public static int simpleField; Signature: I flags: ACC_PUBLIC, ACC_STATIC
不过在实例的构造方法中却再也找 不到对应的初始化代码了。因为static变量会在类的构造方法 中进行初始化,并且它用的是putstatic指令而不是putfiled。
static {}; Signature: ()V flags: ACC_STATIC Code: stack=1, locals=0, args_size=0 0: bipush 100 2: putstatic #2 // Field simpleField:I 5: return
未完待续。
- Java字节码运行浅析
- Java字节码运行浅析
- Java字节码运行浅析
- Java字节码运行浅析
- Java字节码运行浅析
- Java字节码运行浅析
- Java字节码运行浅析(二)
- javaSE-java字节码运行浅析
- Java字节码运行浅析(二)
- Java字节码浅析
- 浅析Java 字节码
- Java字节码浅析(二)
- java虚拟机字节码执行引擎浅析
- Java虚拟机字节码执行引擎浅析
- Java字节码浅析(—)
- Java虚拟机字节码执行引擎浅析
- Java字节码浅析(二)
- Java字节码浅析(三)循环语句
- 烟雾弥漫沙:浅析沙漠如何传播空气污染
- java读取properties文件
- Java +=操作符
- 设计模式之工厂模式
- (二)设计模式之创建型
- Java字节码运行浅析
- redhat 5.9下开启ftp服务
- php条件语句(2)switch...case语句
- Android之-EditText不弹出输入法设置
- ZOJ Problem Set - 1755 Clay Bully
- 摩羯天蝎谁是腹黑帝
- USB OTG驱动分析(一)
- Nyoj 43 24 Point game
- 谷歌排名算法变革反思,站长觉悟吧