英文原文链接,译文链接,原文作者: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了,这条指令会接受一个操作数,对应的是局部变量区中的位置信息。

这条指令执行的时候,内存布局是这样的:

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."":()V

4: aload_0

5: bipush 100

7: putfield #2 // Field simpleField:I

10: return

aload_0

从局部变量数组中加载一个对象引用到操作数栈的栈顶。尽管这段代码看起来没有构造方法,但是在编译器生成的默认的构造方法里,就会包含这段初始化的代码。第一个局部变量正好是this引用,于是aload_0把this引用压到操作数栈中。aload_0是aload_指令集中的一条,这组指令会将引用加载到操作数栈中。n对应的是局部变量数组中的位置,并且也只能是0,1,2,3。还有类似的加载指令,它们加载的并不是对象引用,比如iload_,lload_,fload_,和dload_, 这里i代表int,l代表long,f代表float,d代表double。局部变量的在数组中的位置大于3的,得通过iload,lload,fload,dload,和aload进行加载,这些指令都接受一个操作数,它代表的是要加载的局部变量的在数组中的位置。

invokespecial

这条指令可以用来调用对象实例的构造方法,私有方法和父类中的方法。它是方法调用指令集中的一条,其它的还有invokedynamic, invokeinterface, invokespecial, invokestatic, invokevirtual.这里的invokespecial指令调用的是父类也就是java.lang.Object的构造方法。

bipush

它是用来把一个字节作为整型压到操作数栈中的,在这里100会被压到操作数栈里。

putfield

它接受一个操作数,这个操作数引用的是运行时常量池里的一个字段,在这里这个字段是simpleField。赋给这个字段的值,以及包含这个字段的对象引用,在执行这条指令的时候,都 会从操作数栈顶上pop出来。前面的aload_0指令已经把包含这个字段的对象压到操作数栈上了,而后面的bipush又把100压到栈里。最后putfield指令会将这两个值从栈顶弹出。执行完的结果就是这个对象的simpleField这个字段的值更新成了100。

上述代码执行的时候内存里面是这样的:

这里的putfield指令的操作数引用的是常量池里的第二个位置。JVM会为每个类型维护一个常量池,运行时的数据结构有点类似一个符号表,尽管它包含的信息更多。Java中的字节码操作需要对应的数据,但通常这些数据都太大了,存储在字节码里不适合,它们会被存储在常量池里面,而字节码包含一个常量池里的引用 。当类文件生成的时候,其中的一块就是常量池:

Constant pool:

#1 = Methodref #4.#16 // java/lang/Object."":()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

#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 // "":()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_0

5: bipush 100

7: 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字节码浅析(—)相关推荐

  1. Java中动态加载字节码的方法 (持续补充)

    文章目录 Java中动态加载字节码的方法 1.利用 URLClassLoader 加载远程class文件 2.利用 ClassLoader#defineClass 直接加载字节码 2.1 类加载 - ...

  2. java装逼的话_Java 源码装逼技能之让人懵逼的符号

    源码就是符号位 + 二级制数值.符号位是第一位,0 表示正数,1 表示负数. Java 中 byte 类型一字节八位,可以表示 [1111 1111 , 0111 1111],取值 [-127,127 ...

  3. ACM试题 - ASCII码排序 - Java中字符与对应ASCII码的转换

    Java中字符转换对应ASCII码有两种方式: 第一种: char c = 'a'; byte b = (byte)c; // b=97 第二种: char c = 'a'; int b = c; / ...

  4. fileinputstream_从Java中的FileInputStream读取字节

    以下示例显示了如何从Java中的FileInputStream读取字节. import java.io.File;import java.io.FileInputStream;public class ...

  5. java电脑类_计算机类在Java中的设计于实现码

    计算机类在Java中的设计于实现码 问题描述: 一台计算机是由主板.CPU.显卡.声卡等部件组成的,这些部件通过接口可以直接安插在主板的插槽上,也就是说只要将这些部件简单的安插在一起就可以成功组装出一 ...

  6. java中字节码_Java字节码执行图示

    ★ 查看具体的执行图示,需要先了解一下 java 线程执行的地方,Java 每一个线程执行字节码指令都是在 jvm 虚拟机栈中完成 " 1.JVM 虚拟机栈 每一条 JVM 线程都有自己私有 ...

  7. JAVA中char占用多少字节_Java中char占用几个字节

    https://www.cnblogs.com/louiswong/p/6062417.html https://www.cnblogs.com/fnlingnzb-learner/p/7272348 ...

  8. java中考勤管理_JAVA人事员工考勤管理(含论文)源码

    此系统可以修改,包安装指导,拍下后联系店主.系统品牌: 其他系统 开发语言: .NET 数据库: Mssql 源码参数 源码类别:[毕业设计] 源码类型:B/S 适合人群: 菜鸟进阶 授权类型:商业版 ...

  9. java中Mark接口_JVM源码分析之Java对象头实现

    原标题:JVM源码分析之Java对象头实现 原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 "365篇原创计划"第十一篇. 今天呢!灯塔君跟大家讲: JVM源码分析之Ja ...

最新文章

  1. 一次特殊的经历和迷茫-小米平板
  2. 牛!开创交互式电脑绘图先河,虚拟现实之父传奇
  3. 多线程(一、线程安全案例)
  4. 2020-08-29
  5. 如何设置Linux时区为东八区
  6. P2587 [ZJOI2008]泡泡堂 神仙贪心
  7. List 集合转换为String
  8. 7-40 奥运排行榜 (25 分)(详解+思路+map+vector做法)兄弟们冲压呀呀呀呀呀呀呀
  9. 算法训练营 重编码_编码训练营之后该做什么-以及如何获得成功
  10. LINQ to SQL语句(1)之Where
  11. HDU 4753 Fishhead’s Little Game(DFS)
  12. [python]python学习笔记(三)——编译
  13. 分布式:分布式系统的设计
  14. 冰丝凉席好不好 冰丝凉席怎样 冰丝凉席简介
  15. 来,新手们,Internet Download Manager,艺术升华
  16. tqdm的使用和例子
  17. 计算某年某月某日是该年中的第几天
  18. hdmi接口线_太厉害了!HDMI线还能这么用,以前不知道真是浪费了
  19. 【二维码】二维码生成
  20. PHP微信公众平台自定义菜单

热门文章

  1. Unity3d查找游戏对象
  2. 我在WordCamp上学到的有关新WordPress Gutenberg编辑器的知识
  3. 开源 free的理解_如何理解任何开源混乱
  4. word文档保存发生错误_文档还是没有发生
  5. 必知必会 | 关于Redis缓存这三大问题,必知必会
  6. Bootstrap3 编译版本的文件结构
  7. CSS 元素的display属性
  8. Python笔记(4) 关键字
  9. html设置窗口最小大小,调整HTML 5画布的大小以适应窗口
  10. db2取数据库日期时间_DB2数据库取得当前时间的正确解析