`
deepinmind
  • 浏览: 451580 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
1dc14e59-7bdf-33ab-841a-02d087aed982
Java函数式编程
浏览量:41632
社区版块
存档分类
最新评论

掌握Java字节码

阅读更多
Java是一门设计为运行于虚拟机之上的编程语言,因此它需要一次编译,处处运行(当然也是一次编写,处处测试)。因此,安装到你系统上的JVM是原生的程序,而运行在它之上的代码是平台无关的。Java字节码就是你写的源代码的中间表现形式,也就是你的代码编译后的产物。你的class文件就是字节码。

简单点说,字节码就是JVM使用的代码集,它在运行时可能会被JIT编译器编译成本地代码。

你玩过汇编语言或者机器代码吗?字节码就是类似的东西,不过业界中许多人也很少会用及它,因为基本没这个必要。然而它对于理解程序运行是很重要的,如果你想在酒吧把某人PK下去,它也非常有用。

首先,我们先看一下字节码的基础知识。先拿表达式’1+2‘为例子,看下它的字节码是如何执行的。1+2可以用逆波兰式记法写成1 2 +。为什么?我们把它压到栈里你就明白了。。



OK,在字节码中我们看到了操作码(iconst_1和iconst_2)以及一条指令(iadd),但不是push和add,不过它们的流程是一样的。实际的指令的长度只有一个字节,所以我们把它称为字节码。一共有256种可能的字节码,但现在只用了大概200条。操作码的前缀是类型,后面是操作名。因此我们前面看到的iconst和iadd分别是整型的常量操作,以及整型的加法指令。

这些都不难理解,不过怎么读取class文件呢。通常来说,如果你用自己的编辑器直接打开class文件的话,你会看到一堆笑脸和方块,点号和一些奇奇怪怪的字符,对吧?答案是使用你的JDK提供的一个代码工具,javap。我们来看下如何使用javap。

public class Main {
    public static void main(String[] args){
        MovingAverage app = new MovingAverage();
    }
}
 


一旦这个类编译成了Main.class文件后,你可以使用这个命令来解压字节码:javap -c Main

Compiled from "Main.java"
public class algo.Main {
  public algo.Main();
       Code:
       0: aload_0
       1: invokespecial #1
       4: return
// Method java/lang/Object."<init>":()V
public static void main(java.lang.String[]);
     Code:
       0: new           #2
       3: dup
       4: invokespecial #3
       7: astore_1
      8: return
}
 


我们可以看到字节码里有一个默认的构造方法以及main方法。顺便说一下,这就是当你没有写构造方法时,Java提供默认的构造方法的方式!构造方法中的字节码只是简单地调用了下super(),而我们的main方法会创建一个MovingAverage的实例然后返回。这个#n字符引用的是一个常量,这个我们可以通过-verbose参数看到:javap -c -verbose Main。返回结果里有意思的是下面这段:

public class algo.Main
  SourceFile: "Main.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant 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           #2
       3: dup
       4: invokespecial #3
       7: astore_1
       8: return
 


现在糊涂了吧?:-)如果我们把这个方法体看成一个数组的话,你会得到下面这个东西:



注意每条指令都可以用16进制表示,因此我们实际会得到这个:



如果我们用16进制编辑器打开class文件的话,也能看到它:



我们可以在16进制编辑器中修改这段字节码,不过还是诚实点吧,这不是你想做的,尤其是在一个刚去完酒吧的周五下午。最好的方式就是使用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/MovingAverage
3: dup
4: invokespecial #3  // Method algo/MovingAverage."<init>":()V
7: astore_1
8: iconst_1
9: istore_2
10: iconst_2
11: istore_3
12: aload_1
13: iload_2
14: i2d
15: invokevirtual #4        // Method algo/MovingAverage.submit:(D)V
18: aload_1
19: iload_3
20: i2d
21: invokevirtual #4        // Method algo/MovingAverage.submit:(D)V
24: aload_1
25: invokevirtual #5        // Method algo/MovingAverage.getAvg:()D
28: dstore     4
40LocalVariableTable:
Start  Length  Slot  Name   Signature
0       31         0    args   [Ljava/lang/String;
8       23        1      ma     Lalgo/MovingAverage;
10      21         2     num1   I
12       19         3      num2   I
30       1        4    avg     D
 


看起来更有意思了。。。我们看到这里创建了一个MovingAverage类型的对象,并通过astroe_1指令(1是LocalVariableTable里面的变量槽的位置)存储到了本地变量ma里。指令 iconst_1和iconst_2是用来加载常量1和2到栈里,然后再通过istore_2和istore_3将它们分别存储到LocalVariableTable中第2和第3的位置那。一条load指令将本地变量压到了栈里,而store指令将栈顶的元素弹出,并存储到LocalVariableTable里。很重要的一点是,如果你使用store指令的话,该元素就从栈中移出了,如果你想再操作它的话,得重新加载进来才行。

那执行中的流程控制是怎样的呢?我们看到的只是一行到下一行的顺序执行而已。我想看到GOTO 10这样的组合!我们来再看一个例子:


MovingAverage ma = new MovingAverage();
for (int number : numbers) {
    ma.submit(number);
}
 


在这个例子中,当我们遍历for循环的时候,执行流程会不停地进行跳转。假设这个numbers变量是一个静态变量,那对应的字节码就像是下面这样:

0: new #2 // class algo/MovingAverage
3: dup
4: invokespecial #3 // Method algo/MovingAverage."<init>":()V
7: astore_1
8: getstatic #4 // Field numbers:[I
11: astore_2
12: aload_2
13: arraylength
14: istore_3
15: iconst_0
16: istore 4
18: iload 4
20: iload_3
21: if_icmpge 43
24: aload_2
25: iload 4
27: iaload
28: istore 5
30: aload_1
31: iload 5
33: i2d
34: invokevirtual #5 // Method algo/MovingAverage.submit:(D)V
37: iinc 4, 1
40: goto 18
43: return
LocalVariableTable:
Start  Length  Slot  Name   Signature
30       7         5    number I
12       31        2    arr$     [I
15       28        3    len     $I
18       25         4     i$      I
0       49         0     args  [Ljava/lang/String;
8       41         1    ma     Lalgo/MovingAverage;
48      1         2    avg    D
 


8到17的指令是用来设置这个循环的。LocalVariable表中有三个变量,它们在源码中是不存在的,arr$, len$以及i$。这些都是循环中会用到的变量。arr$存储的是numbers字段的引用,从它这能获取到数组的长度,len$。i$是循环的计数器,iinc指令会去增加它的值。

首先我们需要对循环的条件表达式进行测试,这个可以通过一个比较指令来完成:

18: iload 4
20: iload_3
21: if_icmpge 43
 


我们将4和3压到了栈里,这是循环的计数器以及循环的长度。我们检查 i$ 是不是大于等于len$。如果是的话,跳转到43处的语句,否则继续执行。我们可以在循环体中处理自己的逻辑,结束的时候会增加计数器的值,并跳转回代码中18行处的判断循环条件的语句那。

37: iinc       4, 1       // increment i$
40: goto       18         // jump back to the beginning of the loop
 



字节码中有许多算术运算的操作码和类型的组合,包括如下这些:



前面那个例子中我们把一个整型传递给了接收double类型的submit方法里。Java的语法是允许这样的,不过在字节码中,你可以看到实际用到了i2d操作码:

31: iload 5
 
?33: i2d?
 
34: invokevirtual #5 // Method algo/MovingAverage.submit:(D)V
 



看吧,你已经掌握了这么多了。做的不错,该喝杯咖啡犒劳一下自己了。了解这些东西真的有用吗,还是感觉更geek一些而已?其实两者都有。首先,从现在开始你可以告诉你的朋友,你就是台能处理字节码的JVM了,第二,当你在编写字节码的时候,你会更清楚自己在做些什么。比方说,当你在用ObjectWeb ASM这个广泛使用的操作字节码的工具时,你会需要自己来构造指令,这时候你会发现这些知识太有用了!

原创文章转载请注明出处:http://it.deepinmind.com

英文原文链接
3
2
分享到:
评论

相关推荐

    掌握 Java 字节码的基本操作指令(反汇编字节码)

    掌握 Java 字节码的基本操作指令(反汇编字节码)

    轻松看懂Java字节码.pdf

    标题《轻松看懂Java字节码.pdf》中隐藏...了解和掌握Java字节码,对于Java开发人员来说是提升技术水平的一个重要方面。通过实践和使用相关工具,可以使得阅读和分析字节码变得更加容易,从而更好地优化和维护Java程序。

    java字节码例子 可以动态修改类 bcel

    Java字节码是Java平台的...通过学习这些内容,开发者可以掌握Java字节码的基本操作,并利用BCEL在实际项目中实现高级功能。同时,这也是一种深入理解JVM工作原理的方式,有助于提升Java编程的技能和对底层机制的理解。

    Recaf一个现代Java字节码编辑器

    **Recaf:现代Java字节码编辑器** 在Java开发领域,字节码编辑器是一种不可或缺的工具,...在实际使用中,配合其提供的文档和社区资源,开发者可以更深入地掌握Java字节码操作的技巧,提升开发效率和解决问题的能力。

    java字节码编辑器

    Java字节码编辑器是一种工具,它允许开发者直接编辑Java程序编译后的`.class`文件,而不是反编译后再重新编译。这种编辑器对于理解、调试和优化Java代码非常有用,尤其是对于那些无法访问源代码或者需要进行底层操作...

    java字节码文件查看工具,查看class文件

    Java字节码文件查看工具,如JD-GUI,是开发者们深入理解Java应用程序内部机制的重要辅助工具。这类工具能够帮助我们查看并分析.class文件,这些文件是Java源代码经过编译后的二进制形式,包含了运行时所需的所有指令...

    Java字节码(.class文件)格式详解((转载)

    Java字节码是Java程序编译后的产物,它位于`.class`文件中,是Java虚拟机(JVM)运行的基础。这篇详解将深入探讨字节码的结构和组成,帮助理解Java...通过这些资源,开发者可以系统地学习并掌握Java字节码的各个方面。

    掌握java的bytecode

    掌握Java字节码还可以帮助开发者深入理解JVM的工作原理。JVM在运行时对字节码执行多样的处理,包括加载、验证、链接以及执行等。在某些情况下,开发者可能需要直接与字节码交互,比如使用字节码操作库来动态修改字节...

    Java 字节码简单说明.zip

    Java字节码是Java编程语言的一个重要特性,它在Java程序执行过程中扮演着核心角色。本文将深入探讨Java字节码的基本概念、作用以及其在Java虚拟机(JVM)中的运行机制。 Java字节码是一种低级的、平台无关的指令集...

    字节码解析01.rar

    在Java编程语言中,字节码是连接源代码与机器执行的关键环节。字节码是Java虚拟机(JVM)理解和执行的一种中间...通过实践和进一步学习,开发者将能够全面掌握Java字节码的奥秘,提升自身在Java编程领域的专业技能。

    Java字节码实现Aop

    Java字节码实现AOP(面向切面编程)是一种在程序运行时动态插入代码的技术,它使得我们可以在不修改原有代码的情况下,增加新的功能或监控已有功能。在Java中,AOP通常通过代理模式和字节码操作来实现,如Spring AOP...

    Java字节码反编译

    Java字节码反编译是Java开发者在进行代码分析、逆向工程或学习类库源码时常用的一种技术。它允许我们将已经编译过的.class文件转换回可读性更强的.java源代码文件,以便理解程序的运行逻辑。在这个过程中,我们通常...

    Java字节码指令集的使用详细

    理解并掌握Java字节码指令集对于优化Java代码性能、编写字节码级别的工具,或者进行代码分析和安全审计都是非常重要的。通过深入研究字节码指令,开发者可以更好地理解JVM如何执行程序,从而编写更高效、更可靠的...

    Java字节码和asm入门资料

    Java字节码是Java虚拟机(JVM)执行程序的核心组成部分,它是一种低级的、平台无关的指令集。ASM是一个开源的Java字节码操控和分析框架,它可以直接用来生成和修改Java类文件,是Java动态代理和字节码增强技术的重要...

    java字节码反编译工具

    Java字节码反编译工具是开发者在进行代码分析、逆向工程或学习Java字节码时常用的工具。...无论是进行软件安全分析、性能优化还是学习他人的代码实现,掌握至少一款有效的Java字节码反编译工具都是非常重要的。

    javassist,Java字节码工程工具包.zip

    Java字节码工程工具包,通常被称为Javassist,是一个开源项目,专为Java开发者提供了一种方便的方式来处理和操作字节码。Javassist在Java应用开发中扮演着重要的角色,尤其是在动态代理、AOP(面向切面编程)以及...

    jclazz win JAVA 字节码查看器

    《jclazz:Windows平台上的JAVA字节码查看器详解》 在Java开发过程中,了解和分析字节码是提升程序理解和优化能力的重要手段。jclazz是一款专为Windows平台设计的JAVA字节码查看器,其1.1.2版本以其易用性和功能...

    cglib,字节码生成库是生成和转换Java字节码的高级API。它被aop、测试、数据访问框架用来生成动态代理对象和拦截字段访问。.zip

    **CGlib:高级Java字节码生成库** CGlib(Code Generation Library)是一个强大的高性能的代码生成库,它主要用于在运行期扩展Java类与实现Java接口。这个库最初由Evan Schooler创建,后来成为Apache软件基金会的一...

Global site tag (gtag.js) - Google Analytics