Java字节码的介绍(Introduction to Java Bytecode)

Web应用开发 William 317浏览 0评论

Reading compiled Java bytecode can be tedious, even for experienced Java developers. Why do we need to know about such low-level stuff in the first place? Here is a simple scenario that happened to me last week: I had made some code changes on my machine a long time ago, compiled a JAR, and deployed it on a server to test a potential fix for a performance issue. Unfortunately, the code was never checked into a version control system, and for whatever reason, the local changes were deleted without a trace. After a couple of months, I needed those changes in source form again (which took quite an effort to come up with), but I could not find them!

Luckily the compiled code still existed on that remote server. So with a sigh of relief, I fetched the JAR again and opened it using a decompiler editor… Only one problem: The decompiler GUI is not a flawless tool, and out of the many classes in that JAR, for some reason, only the specific class I was looking to decompile caused a bug in the UI whenever I opened it, and the decompiler to crash!

即便对那些有经验的Java开发人员来说,阅读已编译的Java字节码也很乏味。为什么我们首先需要了解这种底层的东西?这是上周发生在我身上的一个简单故事:很久以前,我在机器上做了一些代码更改,编译了一个JAR,并将其部署到服务器上,以测试性能问题的一个潜在修复方案。不幸的是,代码从未被检入到版本控制系统中,并且出于某种原因,本地更改被删除了而没有追踪。几个月后,我再次修改源代码,但是我找不到上一次更改的版本!

幸运的是编译后的代码仍然存在于该远程服务器上。我于是松了一口气,我再次抓取JAR并使用反编译器编辑器打开它……只有一个问题:反编译器GUI不是一个完美的工具,并且出于某种原因,在该JAR中的许多类中找到我想要反编译的特定类并在我打开它时会在UI中导致了一个错误,并且反编译器崩溃!

Desperate times call for desperate measures. Fortunately, I was familiar with raw bytecode, and I’d rather take some time manually decompiling some pieces of the code rather than work through the changes and testing them again. Since I still remembered at least where to look in the code, reading bytecode helped me pinpoint the exact changes and construct them back in source form. (I made sure to learn from my mistake and preserve them this time!)

The nice thing about bytecode is that you learn its syntax once, then it applies on all Java supported platforms — because it is an intermediate representation of the code, and not the actual executable code for the underlying CPU. Moreover, bytecode is simpler than native machine code because the JVM architecture is rather simple, hence simplifying the instruction set. Yet another nice thing is that all instructions in this set are fully documented by Oracle.

Before learning about the bytecode instruction set though, let’s get familiar with a few things about the JVM that are needed as a prerequisite.

我们可以看到main方法的方法声明,descriptor说明这个方法的参数是一个字符串数组([Ljava/lang/String; ),而且返回类型是void(V)。下面的flags这行说明该方法是公开的(ACC_PUBLIC)和静态的 (ACC_STATIC)。

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

The instructions from address 0 to 8 will do the following:

iconst_1: Push the integer constant 1 onto the operand stack.

iconst_1.png

istore_1: Pop the top operand (an int value) and store it in local variable at index 1, which corresponds to variable a.

istore_1.png

iconst_2: Push the integer constant 2 onto the operand stack.

iconst_2.png

istore_2: Pop the top operand int value and store it in local variable at index 2, which corresponds to variable b.

istore_2.png

iload_1: Load the int value from local variable at index 1 and push it onto the operand stack.

iload_1.png

iload_2: Load the int value from the local variable at index 1 and push it onto the operand stack.

iload_2.png

iadd: Pop the top two int values from the operand stack, add them, and push the result back onto the operand stack.

iadd

istore_3: Pop the top operand int value and store it in local variable at index 3, which corresponds to variable c.

istore_3.png

return: Return from the void method.

Each of the above instructions consists of only an opcode, which dictates exactly the operation to be executed by the JVM.

从地址0到8的指令将执行以下操作:
iconst_1:将整形常量1放入操作数栈。

iconst_1.png

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

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

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

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

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

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

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

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

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

Method Invocations

In the above example, there is only one method, the main method. Let’s assume that we need to a more elaborate computation for the value of variable c, and we decide to place that in a new method called calc:

Let’s see the resulting bytecode:

The only difference in the main method code is that instead of having the iadd instruction, we now an invokestatic instruction, which simply invokes the static method calc. The key thing to note is that the operand stack contained the two arguments that are passed to the method calc. In other words, the calling method prepares all arguments of the to-be-called method by pushing them onto the operand stack in the correct order. invokestatic (or a similar invoke instruction, as will be seen later) will subsequently pop these arguments, and a new frame is created for the invoked method where the arguments are placed in its local variable array.

译者信息

边城

边城
翻译于 3个月前

0 此译文

其它翻译版本:1(点击译者名切换)
kevinlinkai

方法调用

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

看看生成的字节码:

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

译者信息

kevinlinkai

kevinlinkai
翻译于 3个月前

0 此译文

其它翻译版本:1(点击译者名切换)
边城

方法调用

在上面这个例子中,仅仅只是一个main方法。假设,我们需要对变量c的值有一个更精确的计算,并且我们分配一块空间来做一个新的方法,叫做calc。

让我们看看生成的字节码

在main方法代码里与之前方法唯一不同的一点是,iadd 指令被 invokestatic 指令替换了,这是静态方法 calc 的简单调用。需要注意的关键是操作栈包含传递给 calc 方法的两个参数。换句话说,通过按照正确顺序将参数入栈,调用方式准备好了被调用方法的所有参数。invokestatic 指令(或者类似的调用指令,如后面所示)将跟随这些参数出栈,然后为调用方法创建一个新的帧,在该调用方法里将参数放置在本地变量数组中。

转载请注明:AspxHtml学习分享网 » Java字节码的介绍(Introduction to Java Bytecode)

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址