做网站的市场前景,网站建设多少预算,牡丹江商城网站开发设计,网站的首页需要什么内容嘿! Happy Advent#xff1a;D我是ZeroTurnaround的技术布道者Simon Maple#xff08; sjmaple#xff09; 。 您知道#xff0c; JRebel伙计们#xff01; 由于编写了类似于JRebel的产品#xff0c;该产品与字节码进行交互的结果比您想像的更多#xff0c;因此#xf… 嘿! Happy AdventD我是ZeroTurnaround的技术布道者Simon Maple sjmaple 。 您知道 JRebel伙计们 由于编写了类似于JRebel的产品该产品与字节码进行交互的结果比您想像的更多因此我们希望与他人分享很多有关它的知识。 让我们从头开始……Java是一种旨在在虚拟机上运行的语言因此只需要编译一次就可以在任何地方运行是的是的一次编写可以在任何地方进行测试。 结果您安装到系统上的JVM将是本机的从而允许在其上运行的代码与平台无关。 Java字节码是您作为源编写的Java代码的中间表示并且是编译代码的结果。 因此您的类文件是字节码。 更简洁地说Java字节码是Java虚拟机使用的代码集该代码集在运行时被JIT编译为本机代码。 您曾经玩过汇编程序或机器代码吗 从某种意义上说字节码有点类似但是行业内很多人并没有那么多地使用它更多是出于缺乏必要性。 但是了解正在发生的事情非常重要如果您想让酒吧里的某个人望而却步这很有用。 首先让我们看一些字节码基础知识。 我们将首先使用表达式“ 1 2”并查看如何将其作为Java字节码执行。 1 2可以用反向波兰语表示为1 2 。 为什么 好吧当我们将其放在堆栈上时一切都变得清晰了…… 好的在字节码中我们实际上会看到操作码iconst_1和iconst_2和一条指令iadd而不是推和加但流程是相同的。 实际指令的长度为一个字节因此为字节码。 结果有256种可能的操作码但仅使用了200种左右。 操作码的前缀是类型后跟操作名称。 因此我们之前在iconst和iadd上看到的是整数类型的常量和整数类型的加法指令。 这一切都很好但是如何读取类文件。 通常在打开的类文件中通常在选择的编辑器中看到的只是一堆笑脸以及一些正方形圆点和其他奇怪的字符对吗 答案是在Javap中这是您随JDK实际获得的代码实用程序。 让我们看一个代码示例看看运行中的javap。 public class Main {public static void main(String[] args){MovingAverage app new MovingAverage();}} 将此类编译为Main.class文件后我们可以使用以下命令提取字节码javap -c Main Compiled from Main.javapublic class algo.Main {public algo.Main();Code:0: aload_01: invokespecial #14: return
// Method java/lang/Object.init:()V
public static void main(java.lang.String[]);Code:0: new #23: dup4: invokespecial #37: astore_18: return
} 我们可以立即在字节码中看到我们的默认构造函数和main方法。 顺便说一下这就是Java为无构造函数的类提供默认构造函数的方式 构造函数中的字节码只是对super的调用而我们的main方法创建了MovingAverage的新实例并返回。 #n字符实际上是指可以使用-verbose参数查看的常量如下所示javap -c -verbose Main。 返回内容的有趣部分如下所示 public class algo.MainSourceFile: Main.javaminor version: 0major version: 51flags: ACC_PUBLIC, ACC_SUPERConstant pool:#1 Methodref #5.#21 // java/lang/Object.init:()V#2 Class #22 // algo/MovingAverage#3 Methodref #2.#21 // algo/MovingAverage.init:()V#4 Class #23 // algo/Main#5 Class #24 // java/lang/Object 现在我们可以将指令与常量进行匹配并且可以将实际发生的事情拼凑起来要容易得多。 关于上面的示例您还有什么困扰吗 没有 那每条指令前面的数字呢 0: new #23: dup4: invokespecial #37: astore_18: return 现在真的很烦你对吧 :)如果将这个方法体可视化为数组这就是我们得到的 请注意每条指令都有一个十六进制表示因此使用它我们实际上会看到以下内容 如果在十六进制编辑器中打开它我们实际上可以在类文件中看到它 实际上我们可以在HEX编辑器中更改字节码但是老实说这不是您真正想做的事情尤其是在强制性酒吧旅行之后的星期五下午。 更好的方法是使用ASM或javassist。 让我们从基本示例继续添加一些存储状态并直接与堆栈交互的局部变量。 查看以下代码 public static void main(String[] args) {MovingAverage ma new MovingAverage();int num1 1;int num2 2;ma.submit(num1);ma.submit(num2);double avg ma.getAvg();} 让我们看看这次在字节码中得到了什么 Code:
0: new #2 // class algo/MovingAverage3: dup4: invokespecial #3 // Method algo/MovingAverage.init:()V7: astore_18: iconst_19: istore_210: iconst_211: istore_312: aload_113: iload_214: i2d15: invokevirtual #4 // Method algo/MovingAverage.submit:(D)V18: aload_119: iload_320: i2d21: invokevirtual #4 // Method algo/MovingAverage.submit:(D)V24: aload_125: invokevirtual #5 // Method algo/MovingAverage.getAvg:()D28: dstore 4LocalVariableTable:Start Length Slot Name Signature0 31 0 args [Ljava/lang/String;8 23 1 ma Lalgo/MovingAverage;10 21 2 num1 I12 19 3 num2 I30 1 4 avg D 这看起来更加有趣……我们可以看到我们创建了一个类型为MovingAverage的对象该对象通过astore_1指令1是LocalVariableTable中的插槽号存储在本地变量ma中。 指令iconst_1和iconst_2在那里将常数1和2加载到堆栈中并分别通过指令istore_2和istore_3将它们存储在LocalVariableTable插槽2和3中。 一条加载指令将一个局部变量压入堆栈一条存储指令从堆栈中弹出下一项并将其存储在LocalVariableTable中。 重要的是要认识到当使用存储指令时该项目将从堆栈中取出如果要再次使用它则需要加载它。 执行流程如何 我们所看到的只是从一行到下一行的简单进展。 我希望看到一些BASIC风格的GOTO 10混在一起 让我们再举一个例子 MovingAverage ma new MovingAverage();for (int number : numbers) {ma.submit(number);} 在这种情况下当我们遍历for循环时执行流程将跳很多次。 假定数字变量是同一类中的静态字段则此字节码如下所示 0: new #2 // class algo/MovingAverage3: dup4: invokespecial #3 // Method algo/MovingAverage.init:()V7: astore_18: getstatic #4 // Field numbers:[I11: astore_212: aload_213: arraylength14: istore_315: iconst_016: istore 418: iload 420: iload_321: if_icmpge 4324: aload_225: iload 427: iaload28: istore 530: aload_131: iload 533: i2d34: invokevirtual #5 // Method algo/MovingAverage.submit:(D)V37: iinc 4, 140: goto 1843: returnLocalVariableTable:Start Length Slot Name Signature30 7 5 number I 12 31 2 arr$ [I15 28 3 len $I 18 25 4 i$ I0 49 0 args [Ljava/lang/String;8 41 1 ma Lalgo/MovingAverage; 48 1 2 avg D 从位置8到17的指令用于设置循环。 SourceVariable表中有三个在源代码中并未真正提及的变量arr $len $和i $。 这些是循环变量。 arr $存储number字段的参考值从中得出循环长度len $。 i $是循环计数器由iinc指令递增。 首先我们需要测试我们的循环表达式该表达式由比较指令执行 18: iload 420: iload_321: if_icmpge 43 我们将4和4加载到堆栈上分别是循环计数器和循环长度。 我们正在检查ID i $大于或等于len $。 如果是则跳至语句43否则继续进行。 然后我们可以在循环中执行逻辑最后我们增加计数器并跳回到检查语句18的循环条件的代码。 37: iinc 4, 1 // increment i$40: goto 18 // jump back to the beginning of the loop 可以在字节码中使用一堆算术操作码和类型命令组合包括以下内容 以及许多类型转换操作码这些类型转换操作码在为long类型的变量分配一个整数时很重要。 在我们的珍贵示例中我们将一个整数传递给一个采用双精度值的submit方法。 Java语法为我们完成了此操作但是在字节码中您将看到使用了i2d操作码 31: iload 533: i2d
34: invokevirtual #5 // Method algo/MovingAverage.submit:(D)V 因此您已经做到了这一点。 做得好您已经喝咖啡了 了解这些内容是否真的有用还是仅仅是怪胎 好吧两者都有 首先您现在可以告诉您的朋友您是可以处理字节码的JVM其次您可以更好地了解编写字节码时的操作。 例如使用ObjectWeb ASM这是使用最广泛的字节码操作工具之一时您会发现自己正在构造指令并且这些知识将证明是无价的 如果您发现这有趣并且想了解更多请查看ZeroTurnaround的JRebel产品负责人Anton Arhipov的免费Mastering Java Bytecode报告。 JRebel使用javassist我们学习了很多有趣的东西并且可以与Java字节码进行交互该报告更深入地介绍了如何使用ASM。 谢谢阅读 让我知道你的想法 sjmaple 参考在Java Advent Calendar博客上从我们的JCG合作伙伴 Attila Mihaly Balazs 掌握Java字节码 。 翻译自: https://www.javacodegeeks.com/2013/12/mastering-java-bytecode.html