Java字节码浅析(—)
英文原文链接,译文链接,原文作者: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 Signature0 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数组里的字段会多出一段描述信息。
1 public int simpleField; 2 Signature: I 3 flags: ACC_PUBLIC
初始化变量的字节码会被加到构造方法里,像下面这样:
1 public SimpleClass(); 2 Signature: ()V 3 flags: ACC_PUBLIC 4 Code: 5 stack=2, locals=1, args_size=1 6 0: aload_0 7 1: invokespecial #1 // Method java/lang/Object."<init>":()V 8 4: aload_0 9 5: bipush 100 10 7: putfield #2 // Field simpleField:I 11 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中的字节码操作需要对应的数据,
但通常这些数据都太大了,存储在字节码里不适合,它们会被存储在常量池里面,
而字节码包含一个常量池里的引用 。当类文件生成的时候,其中的一块就是常量池:
1 Constant pool: 2 #1 = Methodref #4.#16 // java/lang/Object."<init>":()V 3 #2 = Fieldref #3.#17 // SimpleClass.simpleField:I 4 #3 = Class #13 // SimpleClass 5 #4 = Class #19 // java/lang/Object 6 #5 = Utf8 simpleField 7 #6 = Utf8 I 8 #7 = Utf8 <init> 9 #8 = Utf8 ()V 10 #9 = Utf8 Code 11 #10 = Utf8 LineNumberTable 12 #11 = Utf8 LocalVariableTable 13 #12 = Utf8 this 14 #13 = Utf8 SimpleClass 15 #14 = Utf8 SourceFile 16 #15 = Utf8 SimpleClass.java 17 #16 = NameAndType #7:#8 // "<init>":()V 18 #17 = NameAndType #5:#6 // simpleField:I 19 #18 = Utf8 LSimpleClass; 20 #19 = Utf8 java/lang/Object
常量字段(类常量)
带有final标记的常量字段在class文件里会被标记成ACC_FINAL.
比如
1 public class SimpleClass { 2 3 public final int simpleField = 100; 4 5 }
字段的描述信息会标记成ACC_FINAL:
1 public static final int simpleField = 100; 2 Signature: I 3 flags: ACC_PUBLIC, ACC_FINAL 4 ConstantValue: int 100
对应的初始化代码并不变:
1 4: aload_0 2 5: bipush 100 3 7: putfield #2 // Field simpleField:I
静态变量
带有static修饰符的静态变量则会被标记成ACC_STATIC:
1 public static int simpleField; 2 Signature: I 3 flags: ACC_PUBLIC, ACC_STATIC
不过在实例的构造方法中却再也找不到对应的初始化代码了。
因为static变量会在类的构造方法中进行初始化,并且它用的是putstatic指令而不是putfiled。
1 static {}; 2 Signature: ()V 3 flags: ACC_STATIC 4 Code: 5 stack=1, locals=0, args_size=0 6 0: bipush 100 7 2: putstatic #2 // Field simpleField:I 8 5: return
未完待续。
本文最早发表于本人个人博客:Java译站
转载于:https://www.cnblogs.com/heart-king/p/5386167.html
Java字节码浅析(—)相关推荐
- 如何阅读JAVA 字节码(一)
在阅读JAVA字节码以前,需要回忆一下JVM的结构: Java字节码的信息主要在Java栈中间体现,下图来自网络,描述了java栈的基本结构: 值得注意的是方法区,在Java虚拟机中,方法 ...
- 通过Java字节码发现有趣的内幕之String篇(上)(转)
原文出处: jaffa 很多时候我们在编写Java代码时,判断和猜测代码问题时主要是通过运行结果来得到答案,本博文主要是想通过Java字节码的方式来进一步求证我们已知的东西.这里没有对Java字节码知 ...
- 字节跳动_掌握Java字节码
字节跳动 嘿! 来临快乐:D我是ZeroTurnaround的技术布道者Simon Maple( @sjmaple) . 您知道, JRebel伙计们! 由于编写了类似JRebel的产品,该产品与字节 ...
- JVM 内部原理(七)— Java 字节码基础之二
JVM 内部原理(七)- Java 字节码基础之二 介绍 版本:Java SE 7 为什么需要了解 Java 字节码? 无论你是一名 Java 开发者.架构师.CxO 还是智能手机的普通用户,Java ...
- Java字节码进制转换
在理解二进制和十进制.十六进制时发现的,作为后续参考保存! public class codeCon {/*** 字符串转换成十六进制值* @param bin String 我们看到的要转换成十六进 ...
- 3种骚操作,教你查看 Java 字节码!
在我们工作.学习.以及研究 JVM 过程当中,不可避免的要查看 Java 字节码,通过查看字节码可以了解一个类的编译结果,也能通过编译器层面来分析一个类的性能. 字节码文件是不能直接打开的,下面栈长教 ...
- Java字节码(.class文件)格式详解(一)
原文链接:http://www.blogjava.net/DLevin/archive/2011/09/05/358033.html 小介:去年在读<深入解析JVM>的时候写的,记得当时还 ...
- JAVA字节码指令iload_n为什么只有0到3?
点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取 后台回复"k8s",可领取k8s资料 来源:r6d.c ...
- java 字节码增强原理_深入浅出Java探针技术1--基于java agent的字节码增强案例
Java agent又叫做Java 探针,本文将从以下四个问题出发来深入浅出了解下Java agent 一.什么是java agent? Java agent是在JDK1.5引入的,是一种可以动态修改 ...
最新文章
- C#“.NET研究”类类型
- Java客户端访问HBase集群解决方案(优化)
- python怎么读文件里的指定几行-Python从文件中读取指定的行以及在文件指定位置写入...
- gocron - 定时任务管理系统
- linux kernel中cache代码解读
- [云炬创业基础笔记]第二章创业者测试5
- 2020年牛客多校第五场C题-easy(纯组合计数不要生成函数的做法)
- 更新SQL Server实例所有数据库表统计信息
- 线元法输入曲线要素_Origin入门教程(三):Origin中曲线怎么平移?
- 数据分析:数据分析工具:SPSS、RapidMiner、KNIME、Kettle
- IDEA jsp页面代码没有高亮
- parallels desktop win7远程桌面第一个字符shift键不生效
- 机器学习实战----初识泰坦尼克
- 10:1,AlphaStar横空出世,碾压星际争霸2人类职业玩家 TLO 和 MaNa
- IdentityHashMap 源代码
- 互联网重提内容为王?学Netflix(奈飞)做好内容营销
- 全球与中国老年人代步车市场深度研究分析报告
- 【this,super关键字使用】经典习题
- vs2017 redist 下载地址
- SCA软件成分分析 简析(一)
热门文章
- 网络推广外包中有哪些不可取行为值得网络推广外包专员警示?
- 档案盒正面标签制作_包材工艺丨浅述模内标签印刷及材料的选择
- 如何查看笔记本电脑配置参数_2020双十一(小白/学生)如何选购笔记本电脑?5000元预算哪款笔记本配置值得入手?...
- phantomjs 抓取html,javascript – 如何使用PhantomJS获取网站的HTML源代码
- mysql 行自动增量为23,Mysql Innodb:自动增量非主键
- android 数字时钟代码大全,Android自定义view实现数字时钟
- 猫和老鼠服务器维修有问题,猫和老鼠:游戏中大范围的断网掉线问题 玩家:土豆服务器就这样...
- sphinx索引分析续
- Python操作dict时避免出现KeyError的几种方法
- 记录ie暂时遇到的问题