java 昵称1到32位字符_Java字节码的介绍

即便对那些有经验的Java

幸运的是编译后的代码仍然存在于该远程服务器上。我于是松了一口气,我再次抓取JAR并使用反编译器编辑器打开它……只有一个问题:反编译器G

绝望的时候需要采取孤注一掷的措施。幸运的是,我对原始字节码很熟悉,我宁愿花些

字节码的好处是,您可以只用学习它的语法一次,然后它适用于 所有Java支持的平台 ——因为它是代码的中间表示,而不是底层CPU的实际可执行代码。此外,字节码比本机代码更简单,因为JVM架构相当简单,因此简化了指令集,另一件好事是,这个集合中的所有指令都是由

不过,在学习字节码指令集之前,让我们熟悉一下JVM的一些事情,这是进行下一步的先决条件。

JVM

Java是静态类型的,它会影响字节码指令的设计,这样指令就会期望自己对特定类型的值进行操作。 例如,就会有好几个add指令用于两个数字相加:iadd、ladd、fadd、dadd。 他们期望类型的操作数分别是int、long、float和double。 大多数字节码都有这样的特性,它具有不同形式的相同功能,这取决于操作数类型。

JVM定义的数据类型包括:

基本类型:数值类型: byte (8位), short (16位), int (32位), long (64-bit位), char (16位无符号Unicode), float(32-bit IEEE 754 单精度浮点型), double (64-bit IEEE 754 双精度浮点型)

布尔类型

指针类型: 指令指针。

引用类型:

数组

接口

在字节码中布尔类型的支持是受限的。举例来说,没有结构能直接操作布尔值。布尔值被替换转换成 int 是通过编译器来进行的,并且最终还是被转换成 int 结构。

Java

基于栈的架构

字节码指令集的简单性很大程度上是由于 Sun 设计了基于堆栈的 VM 架构,而不是基于寄存器架构。有各种各样的

PC寄存器:对于Java程序中每个正在运行的

JVM 栈:对于每个线程, 都会分配一个 栈 ,其中存放本地变量、方法 下面是一个显示3个线程的堆栈示例。

dd0bc271f7005369ec3bada5598e1c4a.png

堆:所有线程共享的内存和存储对象(类 对象回收是由垃圾收集器

e03f3cd65c8278e72417803cea4790e4.png

方法区: 对于每个已加载的类,它储存方法的代码和一个符号表(例如对字段或方法的引用)和常量池。

d007b0ad08568a362460e4d62e284114.png

JVM堆栈是由帧组成的,当方法被调用时,每个帧都被推到堆栈上,当方法完成时从堆栈中弹出(通过正常返回或抛出异常)。 每一帧还包括:

本地变量数组,索引从0到它的长度-1。 长度是由编译器计算的。 一个局部变量可以保存任何类型的值,long和double类型的值占用两个局部变量。

用来存储中间值的栈,它存储指令的操作数,或者方法调用的 参数 。

24ceb223f34464c99c3d070409140b8c.png

字节码探索

关于JVM内部的看法,我们能够从示例代码中看到一些被生成的基本字节码例子。

opcode (1 byte)      operand1 (optional)      operand2 (optional)      …

这个指令是由一个一字节的opcode和零个或若干个operand组成的,这个operand包含了要被操作的数据。

在当前执行方法的栈帧里,一条指令可以将值在操作栈中入栈或出栈,可以在本地变量数组中悄悄地加载或者存储值。让我们来看一个例子:

06cce2e6a28aa8e7bf0469de219b1cff.png

为了打印被编译的类中的结果字节码(假设在Test.class文件中),我们运行

javap -v Test.class

我们可以得到如下结果:

488fac85af9b7ca639db8975f1b14aed.png

我们可以看到main方法的方法声明,descr

Code属性是最重要的部分,它包含了这个方法的一系列指令和信息,这些信息包含了操作栈的最大深度(本例中是2)和在这个方法的这一帧中被分配的本地变量的数量(本例中是4)。所有的本地变量在上面的指令中都提到了,除了第一个变量(索引为0),这个变量保存的是args参数。其他三个本地变量就相当于

从地址0到8的指令将执行以下操作:

iconst_1:将整形常量1放入操作数栈。

77846e17d4301e9578b2d1fab5e65c4b.png

istore_1:在索引为1的位置将第一个操作数出栈(一个int值)并且将其存进本地变量,相当于变量a。

1ce1a9c6733977370a475d83894003f6.png iconst_2:将整形常量2放入操作数栈。

88f9c3b3b6279bb23b81ca5a4cd20194.png istore_2:在索引为2的位置将第一个操作数出栈并且将其存进本地变量,相当于变量b。

0ba376186c0287db19c2df930657eaa5.png iload_1:从索引1的本地变量中加载一个int值,放入操作数栈。

62917a785b547474f33b364f9770fdac.png iload_2:从索引2的本地变量中加载一个int值,放入操作数栈。

19270b27ffb14d1962912d420aae2c39.png iadd:把操作数栈中的前两个int值出栈并相加,将相加的结果放入操作数栈。

ad0d7772e7a5e9952204c749772b1ce7.png istore_3:在索引为3的位置将第一个操作数出栈并且将其存进本地变量,相当于变量c。

67b83a3d366675a4d58d55d345f03fde.png return:从这个void方法中返回。

上述指令只包含操作码,由JVM来精确执行。

方法调用

上面的示例只有一个方法,即 main 方法。假如我们需要对变量 c 进行更复杂的计算,这些复杂的计算写在新方法 calc 中:

c5000d27c1b36cadb5f35c8cb4039a22.png

看看生成的字节码:

6abdd5d9c947080f7ba63833d3f307cf.png

main 方法代码唯一的不同在于用 invokestatic 指令代替了 iadd 指令,invokestatic 指令用于调用静态方法 calc。注意,关键在于操作数栈中传递给 calc 方法的两个参数。也就是说,调用方法需要按正确的顺序为被调用方法准备好所有参数,交依次推入操作数栈。iinvokestatic(还有后面提到的其它类似的调用指令)随后会从栈中取出这些参数,然后为被调用方法创建一个新的环境,将参数作为局域变量置于其中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值