`
zhaohaolin
  • 浏览: 1012582 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

提高代码质量及字节码如何防止内存错误

阅读更多

大多Java 程序员 知道他们的程序通常不会被编译为本机代码而是被编译为由java虚拟机(JVM)执行的字节码格式。然而,很少有java程序员曾经看过字节码因为他们的工具不鼓励他们去看。大多Java 调试工具不允许单步执行字节码,它们要么显示源代码行,要么什么也不显示。

  幸运的是JDK提供了javap,一个命令行工具,它使得查看字节码很容易。让我们看一个范例:

  public class ByteCodeDemo {
   public static void main(String[] args) {
    System.out.println("Hello world");
   }
  }

  在编译这个类后,你可以用十六进制编辑器打开.class文件然后参照虚拟机规范翻译字节码。幸运的是有更简单的方法。JDK包含一个命令行的 反汇编器:javap,它可以转换字节码为一种可读的助记符形式,可以像下面这样通过传递'-c'参数给javap得到字节码列表:

  javap -c ByteCodeDemo

  你应该会看到输出类似这样:

  public class ByteCodeDemo extends java.lang.Object {
   public ByteCodeDemo();
   public static void main(java.lang.String[]);
  }
  Method ByteCodeDemo()
  0 aload_0
  1 invokespecial #1
  4 return
  Method void main(java.lang.String[])
  0 getstatic #2
  3 ldc #3
  5 invokevirtual #4
  8 return

  仅仅从这个短小的列表你可以学到很多字节码的知识。从main方法的第一个指令开始:

  0 getstatic #2

  开始的整数是方法中的指令的偏移值,因此第一个指令以0开始。紧随偏移量是指令的助记符(mnemonic)。在这个范例中,'getstatic' 指令将一个静态成员压入一个称为操作数堆栈的数据结构 ,后续的指令可以引用这个数据结构中的成员。getstatic 指令后是要压入的成员。在这个例子中,要压入的成员是"#2 " 。如果你直接检查字节码,你会看到成员信息没有直接嵌入指令而是像所有由java类使用的常量那样存储 在一个共享池中。将成员信息存储在一个常量池中可以减小字节码指令的大小,因为指令只需要存储常量池中的一个索引而不是整个常量。在这个例子中,成员信息位于常量池中的#2处。常量池中的项目的顺序是和编译器相关的,因此在你的环境中看到的可能不是'#2' 。

  分析完第一个指令后很容易猜到其它指令的意思。'ldc' (load constant) 指令将常量"Hello, World."压入操作数栈。'invokevirtual'指令调用println方法,它从操作数栈弹出它的两个参数。不要忘记一个像println 这样的实例方法有两个参数:上面的字符串,加上隐含的'this'引用。

  字节码如何预防内存错误

  Java语言经常被吹捧为开发互联网 软件的"安全 的"语言。表面上和c++如此相似的代码如何体现安全呢?它引入的一个重要的安全概念是防止内存相关的错误。计算机罪犯利用内存错误在其它情况下安全的程序中插入自己的恶意的代码。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

  在方法的开始,虚拟机将方法的参数放入一个称为局部变量表的数据结构中。将像名字暗示的那样,局部变量表也包含了你声明的任何局部变量。在这个 例子中,方法以三个局部变量表的项开始,这些都是add方法的参数,位置0保存this引用,而位置1和2分别保存float和int参数。

  为了实际的操作这些变量,它们必须被加载(压入)到操作数栈。第一个指令fload_1将位置1处的float压入操作数栈,第二个指令 iload_2将位置2处的int压入操作数栈。这些指令的一个引起注意的事情是指令中的'i'和'f'前缀,这说明Java字节码指令是强类型的。如果 参数的类型和字节码的类型不匹配,VM将该字节码作为不安全的而加以拒绝。更好的是,字节码被设计为只需在类被加载时执行一次这样的类型安全检查。

  这个类型安全是如何加强安全的?如果一个攻击者能够欺骗虚拟机将一个int作为一个float或者相反,它就可以很容易的以一个预期的的方法破 坏计算。如果这些计算涉及银行结余,那么隐含的安全性是很明显的。更危险的是欺骗VM将一个int作为一个Object引用。在大多情况下,这将导致VM 崩溃,但是攻击者只需要找到一个漏洞。不要忘记攻击者不会手工搜索这个漏洞--写出一个程序产生数以亿计的错误字节码的排列是相当容易的,这些排列试图找 到危害VM的幸运的那个。

  字节码的另一个内存安全防护是数组操作。'aastore' 和 'aaload' 字节码操作Java数组并且它们总是检查数组边界。如果调用程序越过了数组尾,这些字节码将抛出一个 ArrayIndexOutOfBoundsException。也许所有最重要的检查都使用分支指令,例如,以if开始的字节码。在字节码中,分支指令 只能转移到同一方法中的其它指令。在方法外可以传递的唯一控制是使它返回:抛出一个异常或者执行一个'invoke'指令。这不仅关闭了很多攻击,同时也 防止由于摇荡引用(dangling reference)或者堆栈冲突而引发的令人厌恶的错误。如果你曾经使用系统调试器打开你的程序并定位到代码中的一个随机的位置,那么你会很熟悉这些错 误。

  所有这些检查中需要记住的重要的一点是它们是由虚拟机在字节码级进行的而不是仅仅由编译器在源代码级进行的。一个例如c++这样的语言的编译器可能在编译时预防上面讨论的某些内存错误,但是这些保护只是在源代码级应用。操作系统 将很乐意加载执行任何机器码,无论这些代码是由精细的c++编译器产生的还是心怀恶意的攻击者产生的。简单的讲,C++ 仅仅是在源代码级上面向对象而Java的面向对象的特性扩展到编译过的代码级。

 

分析字节码提升代码质量

  Java 字节码的内存和安全 保护无论我们是否注意都是存在地,那么我们为什么还费心查看字节码呢?在很多情况下,知道编译器如何将你的代码转换为字节码可以帮助你写出更高效的代码,而且在某些情况下可以防止不易发觉的错误。考虑下面的例子:

  //返回 str1+str2 的串连
  String concat(String str1, String str2) {
   return str1 + str2;
  }
  //将 str2 附加到 str1
  void concat(StringBuffer str1, String str2) {
   str1.append(str2);
  }

  猜猜每个方法需要多少个方法调用。现在编译这些方法并且运行javap,你会得到类似下面的输出:

  Method java.lang.String concat1(java.lang.String, java.lang.String)
  0 new #5
  3 dup
  4 invokespecial #6
  7 aload_1
  8 invokevirtual #7
  11 aload_2
  12 invokevirtual #7
  15 invokevirtual #8
  18 areturn
  Method void concat2(java.lang.StringBuffer, java.lang.String)
  0 aload_1
  1 aload_2
  2 invokevirtual #7
  5 pop
  6 return

  concat1方法执行了5个方法调用s: new, invokespecial和三个invokevirtuals,这比concat2方法执行了更多的工作,后者只执行了一个invokevirtual调用。大多Java程序员 已 经得到过警告,因为String是不可变的,而使用StringBuffer进行字符串连接效率更高。使用javap分析这个使得这点变得很生动。如果你 不能肯定两个语言构造在性能上是否相等,你应该使用javap分析字节码。然而,对just-in-time (JIT)编译器要小心,因为JIT编译器将字节码重新编译为本机代码而能执行一些javap不能揭示的附加优化。除非你有你的虚拟机的源代码,否则你应 该补充你的字节码的基准性能分析。

  最后的一个范例展示了检查字节码如何帮助防止程序中的错误。像下面那样创建两个类,确保它们在独立的文件中。

  public class ChangeALot {
   public static final boolean debug=false;
   public static boolean log=false;
  }

  public class EternallyConstant {
   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(两个都为true)。只重新编译ChangeALot。再次运行EternallyConstant,你将看到下面的输出:

  EternallyConstant beginning execution

  Logging mode is on

  debug变量怎么了?即使你将debug设置为true,信息"Debug mode is on"并没有出现。答案在字节码中。对 EternallyConstant运行javap你会看到:

  Method void main(java.lang.String[])
  0 getstatic #2
  3 ldc #3
  5 invokevirtual #4
  8 getstatic #5
  11 ifeq 22
  14 getstatic #2
  17 ldc #6
  19 invokevirtual #4
  22 return

  惊奇吧!在log成员上有一个'ifeq'检查,而代码根本没有检查debug成员。因为debug成员被标记为final类型,编译器知道 debug成员在运行时永远不会改变,因此它通过移除'if'声明进行优化。这确实是一个非常有用的优化,因为它允许你在程序中嵌入调试代码而在将它设置 为false时不用付出运行时的代价。不幸的是这个优化能够导致主要的编译时混乱。如果你改变一个final成员,你必须记住重新编译任何可能引用该成员 的类。这是因为这个'reference'可能已经经过优化了。Java开发环境不能总是发现这个微妙的相关性,一些能导致非常奇怪的错误。因此,古老的C++ 格言对于java环境仍然有效:"When in doubt, rebuild all."(有疑问,重新编译所有的代码)。

  知道一些字节码的知识对于使用java编程的程序员都是有价值的。javap工具使得查看字节码很容易。有时候,使用javap检查你的代码以期提高性能和捕获特殊的不易察觉的错误时是没有用的。

分享到:
评论

相关推荐

    用FindBugs提高代码质量.doc

    《用FindBugs提高代码质量》 在软件开发过程中,确保代码质量是至关重要的,而FindBugs正是这样一款能够帮助开发者提升代码质量的工具。FindBugs是一款静态分析工具,它通过对Java类或JAR文件的字节码进行分析,...

    AS 3 错误代码中文说明

    ### AS 3 错误代码中文说明 #### 1000 系统内存不足 - **描述**:此错误提示表明系统当前...通过以上对 AS 3 错误代码的详细介绍,开发者可以更好地理解各种错误的发生原因及解决方法,从而提高代码质量和开发效率。

    Bytecode Outline-Eclipse插件.rar

    总的来说,Bytecode Outline插件是Java开发者增强其对字节码理解和分析能力的重要工具,尤其在处理JVM级别的问题时,能大大提高开发效率和代码质量。结合Eclipse的强大IDE功能,它能够帮助开发者更有效地进行软件...

    代码虚拟与自动化分析(带完整目录)

    - 静态代码分析:在不运行代码的情况下,通过扫描源代码或字节码来检测潜在问题,如语法错误、代码规范、安全漏洞等。 - 动态代码分析:在代码运行时收集数据,用于性能监控、内存泄漏检测、异常行为分析等。 - ...

    静态代码检查插件之findbug

    FindBugs通过分析字节码来寻找可能的编程错误,而非执行代码。它的核心功能包括检查空指针异常、未初始化的变量、资源泄露、并发问题等多种常见错误。例如,当一个对象可能在没有初始化的情况下被使用时,FindBugs会...

    JAVA复习题7[汇编].pdf

    总的来说,Java是一门强大的编程语言,广泛应用于企业级应用、Web开发、移动应用等领域,其设计哲学和工具集都极大地提升了开发效率和代码质量。掌握Java语言及其生态系统对于任何IT专业人士来说都是至关重要的。

    Java基础面试题.docx

    JDK 9引入了AOT编译,预编译字节码为机器码,减少JIT的预热开销,但AOT的编译质量可能不如JIT。 JDK是Java开发工具集,包含JRE(Java运行时环境)的所有组件,还提供了编译器、文档生成器(javadoc)以及调试器...

    C语言编译器的设计开发-- 字节代码格式设计与实现

    此外,字节代码格式还应该支持垃圾回收机制,以管理程序运行时的内存分配和释放。 总之,C语言编译器的设计和开发是一项复杂而精细的工作,涉及多阶段的转换和优化,其中字节代码格式设计是关键环节,它直接影响到...

    java JVM standard

    了解JVM规范对于Java开发者来说非常重要,它可以帮助我们理解内存管理、性能优化、错误排查等方面的问题,进一步提升代码质量和系统性能。《Java虚拟机规范中文版.pdf》这本书详细阐述了JVM的各个部分,是学习和深入...

    武汉大学Java培训讲义

    - **代码验证**:Java虚拟机中的字节码校验器确保了代码的完整性和安全性,防止伪造的指针、访问权限违规等问题。 - **内存管理**:Java自动管理内存,避免了常见的内存泄漏问题。 - **权限限制**:Java提供了丰富的...

    JAVA代码优化工具

    9. **SonarQube**: SonarQube是一个开源的质量管理平台,它能够检测代码中的各种问题,包括性能问题、代码异味、潜在错误等,帮助团队维持代码质量。 10. **Java Mission Control (JMC)**: JMC是Oracle提供的一个...

    Java虚拟机1111111

    以上是关于Java虚拟机的一些基本知识点,JVM是一个复杂而强大的系统,其深入研究涵盖了诸如内存模型、垃圾收集策略、性能调优等多个方面,对于Java开发人员来说,理解JVM的工作原理对于提升代码质量和优化性能至关...

    深入理解java类加载机制

    在此过程中,JVM会进行安全检查,确保加载的字节码符合JVM规范,防止恶意代码的执行。 验证阶段,JVM对加载的字节码进行详细的语法和语义检查,包括检查类结构的正确性、访问权限、操作数栈和局部变量表的合法性等...

    JAVA基础知识概述

    1. **字节码**:Java编译器将Java源代码编译成一种中间语言——字节码,这种字节码具有统一的格式,不依赖于特定的硬件环境。 2. **Java虚拟机 (JVM)**:不同操作系统上的JVM解释执行相同的字节码,从而确保了Java...

    Analyzing the Analyzers

    Java的分析工具有很多种类,例如JProfiler用于内存和CPU性能分析,FindBugs和SonarQube进行代码质量检查,JaCoCo提供代码覆盖率报告,还有IntelliJ IDEA、Eclipse等IDE内置的分析功能。每种工具都有其特定的用途和...

    findbugs0.9.3桌面版

    总的来说,FindBugs 0.9.3是一款值得信赖的代码错误检查工具,它通过深度分析帮助开发者预防潜在的错误,提高代码质量和可靠性。无论你是初学者还是经验丰富的开发者,都将从中受益匪浅。通过深入理解和熟练应用...

    深入Java虚拟机

    《深入Java虚拟机》这本书是Java开发者不可或缺的参考资料,它涵盖了Java虚拟机(JVM)的内部工作机制,包括类加载机制、内存管理、字节码执行、垃圾收集、性能优化等多个核心主题。以下是对这些知识点的详细阐述: ...

    查找项目中的bug,快捷查找项目中的bug及代码漏洞

    1. **静态分析**:FindBugs不依赖于代码执行,而是通过分析源代码或编译后的字节码来发现潜在问题。这意味着它可以在编码阶段或者构建过程中进行检查,无需等待运行时环境。 2. **丰富的检查规则**:FindBugs包含了...

    4 种主流 Java 静态代码分析工具

    通过分析字节码,FindBugs可以识别出可能导致错误的行为。 **功能**: - **缺陷检测**:基于预定义的缺陷模式,检测潜在的问题。 - **误报处理**:提供机制减少误报的可能性。 - **插件支持**:支持多种IDE集成。 ...

Global site tag (gtag.js) - Google Analytics