`

java字节码

    博客分类:
  • Java
阅读更多

深入 Java 编程—— Java 的字节代码(转自http://xvm03.iteye.com/blog/646540)

 

Java 程序员很少注意程序的编译结果。事实上, Java 的字节代码向我们提供了非常有价值的信息。特别是在调试排除 Java 性能问题时,编译结果让我们可以更深入地理解如何提高程序执行的效率等问题。其实 JDK 使我们研究 Java 字节代码变得非常容易。本文阐述怎样利用 JDK 中的工具查看解释 Java 字节代码,主要包含以下方面的一些内容:

 

l         Java 类分解器—— javap

l         Java 字节代码是怎样使程序避免程序的内存错误

l         怎样通过分析字节代码来提高程序的执行效率

l         利用第三方工具反编译 Java 字节代码

 

一、 Java 类分解器—— javap

   大多数 Java 程序员知道他们的程序不是编译成本机代码的。实际上,程序被编译成中间字节代码,由 Java 虚拟机来解释执行。然而,很少程序员注意一下字节代码,因为他们使用的工具不鼓励他们这样做。大多数的 Java 调试工具不允许单步的字节代码调试。这些工具要么显示源代码,要么什么都不显示。

幸好 JDK 提供了 Java 类分解器 javap ,一个命令行工具。 javap 对类名给定的文件( .class )提供的字节代码进行反编译,打印出这些类的一个可读版本。在缺省情况下, javap 打印出给定类内的公共域、方法、构造函数,以及静态初始值。

1 javap 的具体用法

语法: javap  ...

其中选项包括:

参数

含义

   b

向后兼容 JDK 1.1 中的 javap

   c

反编译代码,打印出每个给定类中方法的 Java 虚拟机指令。使用该选项后,将对包括私有及受保护方法在内的所有方法进行反编译

   classpath <pathlist>

指明到哪里查找用户的类文件。这个选项值覆盖了缺少路径以及由 CLASSPATH 环境变量定义的路径。此处给出的路径是一个目录及 zip 文件有序列表,其元素在 Unix 中以“ : ”,在 Windows 中以“ ; ”分隔。要想在不覆盖缺省系统类路径的情况下增加一些要查找的目录或 zip 文件,应使用 CLASSPATH 环境变量,使用方法与编译器的 -classpath 相同。

   extdirs <dirs>

覆盖安装扩展目录

   help

显示帮助信息

   J<flag>

<flag> 直接传递给运行系统

    l

在原来打印信息的基础上,增加行号和局部变量表

   public

只显示公共类及其成员

   protected

显示受保护 / 公共类及其成员

   package

显示包受保护 / 公共类及其成员(缺省)

   private

显示所有类及其成员

   s

打印内部类型标记

   bootclasspath <pathlist>

覆盖由引导类加载器加载的类文件位置

   verbose

打印堆栈大小,方法的局部变量和参数的数目。若可验证,打印出错原因

2. 应用实例

 

让我们来看一个例子来进一步说明如何使用 javap

// Imports

import java.lang.String;

 

public class ExampleOfByteCode {

  // Constructors

  public ExampleOfByteCode() { }

 

  // Methods

  public static void main(String[] args) {

    System.out.println("Hello world");

  }

}

编译好这个类以后,可以用一个十六进制编辑器打开 .class 文件,再通过虚拟机说明规范来解释字节代码的含义,但这并不是好方法。利用 javap ,可以将字节代码转换成人们可以阅读的文字,只要加上 -c 参数:

javap -c ExampleOfByteCode

输出结果如下:

Compiled from ExampleOfByteCode.java

public class ExampleOfByteCode extends java.lang.Object {

    public ExampleOfByteCode();

    public static void main(java.lang.String[]);

}

 

Method ExampleOfByteCode()

   0 aload_0

   1 invokespecial #6 <method java>

   4 return

 

Method void main(java.lang.String[])

   0 getstatic #7 <field java out>

   3 ldc #1 <string world>

   5 invokevirtual #8 <method void println>

   8 return

从以上短短的几行输出代码中,可以学到关于字节代码的许多知识。在 main 方法的第一句指令是这样的:

0 getstatic #7 <field java out>

开头的初始数字是指令在方法中的偏移,所以第一个指令的偏移是 0 。紧跟偏移的是指令助记符。在本例中, getstatic 指令将一个静态字段压入一个数据结构,我们称这个数据结构为操作数堆栈。后续指令可以通过此结构引用这个字段。紧跟 getstatic 指令后面的是压到哪个字段中去。这里的字段是“ #7 <field java out> ”。如果直接察看字节代码,这些字段信息并没有直接存放到指令中去。事实上,就象所有 Java 类使用的常量一样,字段信息存储在共享池中。在共享池中存储字段信息可以减小字节代码的大小。这是因为指令仅仅需要存储的是整型索引号,而不是将整个常量存储到常量池中。本例中,字段信息存放在常量池的第七号位置。存放的次序是由编译器决定的,所以看到的是“ #7 ”。

通过分析第一行指令,我们可以看出猜测其它指令的含义还是比较简单的。“ ldc ”(载入常量)指令将常量“ Hello, World. ”压入操作数堆栈。“ invokevirtual ”激发 println 方法,此方法从操作数堆栈中弹出两个参数。不要忘记象 println 这样的方法有两个参数:明显的一个是字符串参数,加上一个隐含的“ this ”引用。

二、 Java 字节代码是怎样使程序避免程序的内存错误

Java 程序设计语言一直被称为 internet 的安全语言。从表面上看,这些代码象典型的 C++ 代码,安全从何而来?安全的重要方面是避免程序的内存错误。计算机罪犯利用程序的内存错误可以将他们的非法代码加到其它安全的程序中去。 Java 字节代码是站在第一线抵御这种攻击的

1. 类型安全检测实例

以下的例子可以说明 Java 具体是怎样做的。

public float add(float f, int n) {

return f + n;

}

如果你将这段代码加到第一个例子中去,重新编译,运行 javap ,分析情况如下:

Method float add(float, int)

   0 fload_1

   1 iload_2

   2 i2f

   3 fadd

   4 freturn

Java 方法的开头,虚拟机将方法的参数放到一个被称为举办变量表的数据结构中。从名字就可以看出,局部变量表包含所有声明的局部变量。在本例中,方法从三个局部变量表实体开始,这些是 add 方法的三个参数。位置 0 保存该方法返回类型,位置 1 2 保存浮点和整型参数。

为了真正操纵变量,它们必须被装载(压)到操作数堆栈。第一条指令 fload_1 将浮点参数压到操作数堆栈的位置 1 。第二条指令 iload_2 将整型参数压到操作数堆栈的位置 2 。有趣的是这些指令的前缀是以“ i ”和“ f ”开头的,这表明 Java 字节代码的指令按严格的类型划分的。如果参数类型与字节代码的参数类型不符合,虚拟机将拒绝不安全的字节代码。更妙的是,字节代码被设计成仅执行一次类型安全检查——当加载类的时候。

2.Java 中的类型安全检测

类 型安全是怎样增强系统安全性的呢?如果攻击者可以让虚拟机将整型变量当成浮点变量,或更严重更多,很容易预见计算的崩溃。如果计算是发生在银行账户上的, 牵连的安全问题是很明显的。更危险的是欺骗虚拟机将整型变量编程一个对象引用。在大多数情况下,虚拟机将崩溃,但是攻击者只要找到一个漏洞即可。不要忘记 攻击者不需要手工查找——更好且容易的办法是写一个程序产生大量变换的坏的字节代码,直到找到一个可以危害虚拟机的。

另一种字节代码保护内存安全的是数组操作。“ aastore ”和“ aaload ”字节代码操作 Java 数组,而它们一直要检查数组的边界。当调用者超越数组边界时,这些字节代码将产生数组溢出错误( ArrayIndexOutOfBoundsException )。也许所有应用中最重要的检测是分支指令,例如,以“ if. ”开始的字节代码。在字节代码中,分支指令在同一个方法中只能跳转到另一条指令。向方法之外传递控制的唯一办法是返回,产生一个异常,或执行一个唤醒( invoke )指令。这不仅关闭了许多易受攻击的大门,也防止由伴随引用和堆栈的崩溃导致的可恶的程序错误。如果你曾经用系统调试器打开过代码中随机定位的程序,你对这些程序错误会很熟悉。

需要着重指出的是:所有的这些检测是由虚拟机在字节代码级上完成的,不仅仅是编译器。其它编程语言的编译器象 C++ 的,可以防止一些我们在上面讨论过的内存错误,但这些保护是基于源代码级的。操作系统将读入执行任何机器代码,而不管这些代码是由小心翼翼的 C++ 编译器还是由邪恶的攻击者产生的。简单地说, C++ 是在源程序级上是面向对象的,而 Java 的面向对象特性扩展到已经编译好的字节代码上。

三、怎样通过分析字节代码来提高程序的执行效率

不管你注意它们与否, Java 字节代码的内存和安全保护都客观存在,那为什么还要那么麻烦去看字节代码呢?其实,就如在 DOS 下深入理解汇编就可以写出更好的 C++ 代码一样,了解编译器怎样将你的代码翻译成字节代码可帮助你写出更有效率的代码,有时候甚至可以防止不知不觉的程序错误。

1. 为什么在进行字符串合并时要使用 StringBuffer 来代替 String

我们看以下代码:

//Return the concatenation str1+str2

    String concat(String str1, String str2) {

        return str1 + str2;

    }

 

    //Append str2 to str1

    void concat(StringBuffer str1, String str2) {

        str1.append(str2);

    }

试想一下每个方法需要执行多少函数 。编译该程序并执行 javap ,输出结果如下:

Method java.lang.String concat(java.lang.String, java.lang.String)

   0 new #6 <class java>

   3 dup

   4 aload_1

   5 invokestatic #14 <method java valueof>

   8 invokespecial #9 <method java>

  11 aload_2

  12 invokevirtual #10 <method java append>

  15 invokevirtual #13 <method java tostring>

  18 areturn

 

Method void concat(java.lang.StringBuffer, java.lang.String)

   0 aload_1

   1 aload_2

   2 invokevirtual #10 <method java append>

   5 pop

   6 return

第一个 concat 方法有五个方法调用 new invokestatic invokespecial 和两个 invokevirtual 。这比第二个 cacat 方法多了好多些工作,而第二个 cacat 只有一个简单的 invokevirtual 调用 String 类的一个特点是其实例一旦创建,是不能改变的,除非重新给它赋值。在我们学习 Java 编程时,就被告知对于字符串连接来说,使用 StringBuffer 比使用 String 更有效率。使用 javap 分析这点可以清楚地看到它们的区别。如果你怀疑两种不同语言架构在性能上是否相同时,就应该使用 javap 分析字节代码。不同的 Java 编译器,其产生优化字节代码的方式也不同,利用 javap 也可以清楚地看到它们的区别。以下是 JBuilder 产生字节代码的分析结果:

Method java.lang.String concat(java.lang.String, java.lang.String)

   0 aload_1

   1 invokestatic #5 <method java valueof>

   4 aload_2

   5 invokestatic #5 <method java valueof>

   8 invokevirtual #6 <method java concat>

  11 areturn

可以看到经过 JBuilder 的优化,第一个 concat 方法有三个方法调用:两个 invokestatic invokevirtual 。这还是没有第二个 concat 方法简洁。

不管怎样,熟悉即时编译器 (JIT, Just-in-time) 。因为当某个方法被第一次调用时,即时编译器将对该虚拟方法表中所指向的字节代码进行编译,编译完后表中的指针将指向编译生成的机器码,这样即时编译器将字节代码重新编译成本机代码,它可以使你进行更多 javap 分析没有揭示的代码优化。除非你拥有虚拟机的源代码,你应当用性能基准来进行字节代码分析。

2. 防止应用程序中的错误

以下的例子说明如何通过检测字节代码来帮助防止应用程序中的错误。首先创建两个公共类,它们必须存放在两个不同的文件中。

public class ChangeALot {

    // Variable

    public static final boolean debug=false;

    public static boolean log=false;

}

 

public class EternallyConstant {

    // Methods

    public static void main(String [] args) {

        System.out.println("EternallyConstant beginning execution");

        if (ChangeALot.debug)

            System.out.println("Debug mode is on");

        if (ChangeALot.log)

            System.out.println("Logging mode is on");

    }

}

如果运行 EternallyConstant 类,应该得到如下信息:

EternallyConstant beginning execution.

现在我们修改 ChangeALot 文件,将 debug log 变量的值都设置为 true 。只重新编译 ChangeALot 文件,再运行 EternallyConstant ,输出结果如下:

EternallyConstant beginning execution

Logging mode is on

在调试模式下怎么了?即使设置 debug true ,“ Debug mode is on ”还是打印不出来。答案在字节编码中。运行 javap 分析 EternallyConstant 类,可看到如下结果:

Compiled from EternallyConstant.java

public class EternallyConstant extends java.lang.Object {

    public EternallyConstant();

    public static void main(java.lang.String[]);

}

 

Method EternallyConstant()

   0 aload_0

   1 invokespecial #1 <method java>

   4 return

 

Method void main(java.lang.String[])

   0 getstatic #2 <field java out>

   3 ldc #3 <string beginning execution>

   5 invokevirtual #4 <method void println>

   8 getstatic #5 <field boolean log>

  11 ifeq 22

  14 getstatic #2 <field java out>

  17 ldc #6 <string mode is on>

  19 invokevirtual #4 <method void println>

  22 return

很奇怪吧!由于有“ ifep ”检测 log 字段,代码一点都不检测 debug 字段。因为 debug 字段被标记为 final ,编译器知道 debug 字段在运行过程中不会改变。所以“ if ”语句被优化,分支部分被移去了。这是一个非常有用的优化,因为这使你可以在引用程序中嵌入调试代码,而设置为 false 时不用付出代价,不幸的是这会导致编译混乱。如果改变了 final 字段,记住重新编译其它引用该字段的类。这就是引用有可能被优化的原因。 Java 开发工具不是每次都能检测这个细微的改变,这些可能导致临时的非常程序错误。在这里,古老的 C++ 格言对于 Java 环境来说一样成立:“每当迷惑不解时,重新编译所有程序“。

四、利用第三方工具反编译 Java 字节代码

以上介绍了利用 javap 来分析 Java 字节代码,实际上,利用第三方的工具,可以直接得到源代码。这样的工具有很多,其中 NMI's Java Code Viewer (NJCV) 是其中使用起来比较方便的一种。

1 NMI's Java Code Viewer 简介

NJCV 针对编译好的 Java 字节编码,即 .class 文件、 .zip .jar 文件。 .jar 文件实际上就是 .zip 文件。利用 NJCV 这类反编译工具,可以进一步调试、监听程序错误,进行安全分析等等。通过分析一些非常优秀的 Java 代码,我们可以从中学到许多开发 Java 程序的技巧。

NMI's Java Code Viewer 的最新版本是 4.8.3 ,而且只能运行在以下 Windows 平台:

l         Windows 95/98

l         Windows 2000

l          Windows NT 3.51/4.0

2. NMI's Java Code Viewer 应用实例

我们以前面例举到的 ExampleOfByteCode.class 作为例子。打开 File 菜单中的 open 菜单,打开 Java 字节代码文件, Java class files 中列出了所有与该文件在同一个目录的文件。选择要反编译的文件,然后在 Process 菜单中选择 Decompile Dissasemble ,反编译好的文件列在 Souce-code files 一栏。用 NMI's Java Code Viewer 提供的 Programmer s File Editor 打开该文件,瞧,源代码都列出来了。

// Processed by NMI's Java Code Viewer 4.8.3 © 1997-2000 B. Lemaire

// Website: http://njcv.htmlplanet.com  E-mail: info@njcv.htmlplanet.com

// Copy registered to Evaluation Copy

// Source File Name:   ExampleOfByteCode.java

 

import java.io.PrintStream;

 

public class ExampleOfByteCode {

 

    public ExampleOfByteCode() {

    }

分享到:
评论

相关推荐

    轻松看懂Java字节码.pdf

    标题《轻松看懂Java字节码.pdf》中隐藏的知识点是理解Java字节码的重要性及如何轻松掌握。描述中提到Java字节码是实现“一次编写,到处运行”(Write Once, Run Anywhere)这一Java承诺的核心技术之一。而标签“Java...

    Java字节码转换工具—Retrotranslator

    Java字节码转换工具Retrotranslator是一个用于解决软件兼容性问题的实用工具,尤其是在Java版本升级带来的不兼容性上。随着Java技术的不断迭代,新版本的特性常常不能在旧版本的JDK环境下运行,而Retrotranslator的...

    class运行器v6(可以运行java字节码文件的工具 含代码)

    标题中的“class运行器v6”是一个用于执行Java字节码文件的应用程序,它允许用户在没有完整Java环境的情况下运行单个.class文件。这个工具可能是由开发者为了方便测试或教学目的而创建的,特别是对于那些不熟悉或者...

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

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

    java字节码加密

    Java字节码加密是保护Java应用程序源代码安全的重要技术手段,主要是为了防止恶意用户逆向工程分析、篡改或盗取程序的核心逻辑。在Java中,字节码(Bytecode)是程序经过编译后的中间表示,可以直接由Java虚拟机...

    java字节码编辑器

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

    Java 字节码概述

    Java 字节码概述 Java 字节码是 Java 虚拟机(JVM)执行 Java 语言编译后的结果。Java 字节码是一种平台无关的中间形式,能够在不同的操作系统和硬件平台上运行。Java 字节码的执行是由 JVM 负责的,它将字节码翻译...

    Java字节码优化框架

    Java字节码优化框架,如Soot,是用于提升Java程序性能的重要工具。Soot作为一个独立的工具,能够对Java字节码进行优化和检查,同时也为开发者提供了一个框架,以便于在字节码级别设计和实现优化策略。这个框架支持...

    基于Java字节码的混淆技术研究

    ### 基于Java字节码的混淆技术研究 #### 引言 Java作为一种跨平台的语言,具有良好的移植性和灵活性,被广泛应用于多种场景。Java程序编写完成后,通过编译器将其转换为字节码(一种中间代码),并保存在`.class`...

    java字节码分析工具

    Java字节码分析工具,系统分析了java字节码文件,即java class类文件,对该文件中的各种成分以树的形式描述出来,只能针对未加密的class文件,一般由标准java编译器编译生成的class文件都未加密,该系统在vs2003下面...

    Java字节码简单说明.docx

    Java 字节码简单说明 Java 字节码是 Java 跨平台的基础,它使得 Java 程序可以在不同的平台上运行,而不需要重新编译。Java 字节码是平台无关性的基石,也是语言无关性的基础。 Class 文件结构是 Java 字节码的...

    JByteMod-1.6.1(java字节码编辑器)简介及下载

    使用JByteMod需要一定的Java字节码知识,但它的直观界面和丰富的功能使得这个过程相对平易近人。无论是开发、调试还是研究,JByteMod都是一个强大的工具,能够帮助开发者更深入地理解和操作Java程序。

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

    Java字节码是Java平台的一种独特特性,它使得Java程序具有跨平台的兼容性和高度的安全性。字节码是Java虚拟机(JVM)能够理解的低级指令集,每条字节码指令对应一种特定的操作。在Java中,我们可以使用字节码技术来...

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

    Java字节码是Java程序在运行时被JVM(Java虚拟机)解释执行的一种中间语言。每个Java类都由一个`.class`文件表示,其中包含了编译后的字节码指令。`.class`文件的结构非常严谨,它不仅包含了类的信息,如类名、方法...

    JAVA字节码JAVA字节码.doc

    Java 字节码编程 Java 字节码是 Java 程序的中间表示形式,它可以被 Java 虚拟机(JVM)解释执行。了解 Java 字节码可以帮助开发者更好地理解 Java 程序的执行机制,提高程序的执行效率和排除错误。 一、Java 类...

    java字节码.docx

    Java 字节码解读 Java 字节码是一种中间形式的代码,它是 Java 源代码编译后的结果。Java 字节码是平台独立的,可以在任何支持 Java 的设备上运行。Java 字节码的结构主要包括魔数、主版本号、次版本号、常量池、...

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

    javassist, Java字节码工程工具包 Java字节码工程工具包 版本 3版权所有( C ) 1999 -2017按 Shigeru Chiba,保留所有权利。Javassist ( Java编程助手) 使Java字节码操作简单。 它是一个类库,用于在Java中编辑字节码

    从一个class文件深入理解Java字节码结构

    【Java字节码结构解析】 Java程序在执行时,首先需要通过Java编译器将源代码(.java文件)编译成二进制的字节码文件(.class文件),这些字节码由Java虚拟机(JVM)解析并执行。深入理解字节码结构有助于我们了解...

    Java 字节码简单说明.zip

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

Global site tag (gtag.js) - Google Analytics