`
dev_liu
  • 浏览: 112765 次
  • 性别: Icon_minigender_1
  • 来自: 成都
最近访客 更多访客>>
社区版块
存档分类
最新评论

提高java代码的性能

阅读更多

简介<o:p></o:p>

Java的诸多优点已经广为称道。特别是“一次编程,到处运行”的承诺使开发人员可以自由地进行跨平台应用程序的开发而不存在预处理器指令的开销。通常认为Java的弱点在于其性能方面。

在当前这种认识并不是完全正确的,有很多产品可以提高Java程序的性能并能够使其在很多应用程序中不再成为一个问题。例如,TowerJ是一种将Java字节代码转换成高度优化的本地可执行程序的后期编译器,Jrockit是一种具有自适应优化能力的服务器端的Java虚拟机。尽管如此,运用一些简单的技巧可以使你不必购买上述的这些工具也能够改进Java代码的性能。在文本中我将说明其中的一些。

本文的讨论主要基于那些高吞吐量的代码(服务器端)。鉴于主要的开销是由那些涉及到对象创建和再创建的GUI代码所引起的,对服务器端代码进行有效的性能估算指针是方法的执行时间。因此,对于所涉及到的示例代码,我记录了执行方法所需的平均时间。记录一个方法的精确执行时间并不是切实可行的,因此我对一系列方法进行计时并计算其平均值。这样做有效地模拟了那些以性能为关键的代码的执行。

每个示例都带有对字节代码操作进行解释的伪代码。所产生的实际的字节代码可以从CUJWeb站点(www.cuj.com/code)获取。对所有字节代码的解释可以从Javasoft的站点获得。

改善字符串处理的性能<o:p></o:p>

C++一样,Java库中定义了自己的String类型。在其外表之下,这种类型是由一个char型数组所实现的,然而使用字符串并不需要理解这一点。NULL符(’\0’)是导致很多学生在学习和使用C++的过程中受挫的祸根;使用Java则不必为此分心,程序员可以专注于应用程序本身以及创建应用程序所用的工具。但是存在着与这种省心的字符串处理方式相关的不利方面,那就是字符串的连接操作符‘+’。

这个操作符看起来十分有用。多数需要向流写入数据的应用程序都使用‘+’。例如:

String name = new String("Joe");<o:p></o:p>

System.out.println(name + " is my name.");<o:p></o:p>

在上面的代码段中,看起来似乎在println语句中无法作出什么修改以改善其执行的速度。然而,这个语句所产生的字节代码(在此用伪代码表示)却揭示了事实,见程序清单1

<o:p> </o:p>

清单 1:描述由字符串连接符‘+’所产生的字节代码操作的伪代码

create new String (STR_1)<o:p></o:p>

duplicate the String<o:p></o:p>

load the constant String "Joe" (STR_2)<o:p></o:p>

call String constructor<o:p></o:p>

store this String in the local variable array position 0<o:p></o:p>

get the static out field from the java.io.PrintStream class (OUT)<o:p></o:p>

create a new StringBuffer (STR_BUF_1)<o:p></o:p>

duplicate the StringBuffer<o:p></o:p>

call StringBuffer constructor<o:p></o:p>

store this StringBuffer in the local variable array position 1<o:p></o:p>

invoke the append method on STR_BUF_1 with STR_1 as the argument<o:p></o:p>

load the constant String " is my name." (STR_3)<o:p></o:p>

invoke the append method on STR_BUF_1 with STR_3 as the argument<o:p></o:p>

invoke toString method on STR_BUF_1 (STR_4)<o:p></o:p>

invoke the println method on OUT

<o:p> </o:p>

这段简单的代码创建了5个对象[1]STR_1, STR_2, STR_3, STR_4, and STR_BUF_1

需要注意对象创建相对来讲是非常耗费资源的。必须为每个类和类的每一个超类的所有实例变量分配堆存储空间;所有的实例变量必须被初始化;而且类的构造函数和每个超类的构造函数必须被执行。为了创造高效的代码,只限于在绝对必须的情况下进行对象的创建是十分必要的。

那么,刚才的代码是否可以用一种更高效的方法重写呢?请考虑下面的程序段:

StringBuffer name = new StringBuffer("Joe");<o:p></o:p>

System.out.println(name.append(" is my name.").toString());<o:p></o:p>

相应的字节代码/伪代码请参见程序清单2<o:p></o:p>

<o:p> </o:p>

清单2:使用StringBufferappend操作符的字节代码/伪代码<o:p></o:p>

create new StringBuffer (STR_BUF_1)

duplicate the StringBuffer

load the constant String "Joe" (STR_1)

call StringBuffer constructor

store this StringBuffer in the local variable array position 1

get the static out field from the java.io.PrintStream class (OUT)

load STR_BUF_1

load the constant String " is my name." (STR_2)

invoke the append method on STR_BUF_1 with STR_2 as the argument

invoke toString method on STR_BUF_1 (STR_3)

invoke the println method on OUT

<o:p> </o:p>

上面的代码值创建了4个对象:STR_1, STR_2, STR_3, STR_BUF_1。你可能会认为减少一个对象的创建并不能起到多大作用。然而,县棉的代码创建了8个对象:

String name = new String("Joe");<o:p></o:p>

name+=" is my";<o:p></o:p>

name+=" name.";<o:p></o:p>

而这段代码仅创建了5个:

StringBuffer name = new StringBuffer("Joe");<o:p></o:p>

name.append(" is my");<o:p></o:p>

name.append(" name.").toString();<o:p></o:p>

第二段代码执行的速度比第一段快两倍还多[2]

结论:使用StringBuffer来改进字符串处理代码的性能。其目标是使新对象的创建达到最少,这可以通过使用在StringBuffer之上使用append方法来代替在String上使用连接操作符来实现。

更快的日志记录(faster logging<o:p></o:p>

在我参与开发的每个软件项目中,都要求有一种适当的日志记录机制。在应用程序中包括日志记录功能的原因有很多。主要的原因是为了使维护更加容易。为了在发行的应用程序中实现错误报告,有必要设置开始点。在很多情况下,用户提交的报告含义不清,其描述的问题可能是由多方面因素造成的。如果有一种适当的机制能够使用户收集关于此问题的额外信息,那么解决问题的周期会大大地缩减。

并没有标准的方法来产生这种信息,通常这有赖于开发人员如何适当地建立这种机制。然而,日志记录机制的实现对应用程序的性能会造成较大的影响。我们的目标是建立一种能够输出有价值的运行时信息但同时使其对运行时性能的影响达到最小的机制。

避免运行时开销的最显而易见的办法是不将日志记录包括在发行的应用程序中;换句话说,如果执行日志记录的实际代码没有编译到应用程序中的话,那么也就不会对性能造成影响。程序清单3展示了一个定义这样一种记录机制的类。可以对其进行设置使日志记录代码从所产生的字节代码中忽略。这个类将是一个单元素(Singleton)以避免创建不必要的Logger类的实例。

<o:p> </o:p>

清单3:可以进行配置使其在发布版本中不产生代码的]单元素的Logger

public class Logger {<o:p></o:p>

  // 这个类的实例<o:p></o:p>

  private static Logger theLogger = null;<o:p></o:p>

  // 调试消息(debug messages)的控制器,设为true时允许调试消息,false则反之<o:p></o:p>

  public static final boolean CAN_DEBUG = true;<o:p></o:p>

  /** 私有的构造函数——只允许一个实例 */<o:p></o:p>

  private Logger() {}<o:p></o:p>

  /** 返回这个类所创建的唯一实例 */<o:p></o:p>

  public static Logger getInstance() {<o:p></o:p>

    if(theLogger == null) { theLogger = new Logger(); }<o:p></o:p>

    return theLogger;<o:p></o:p>

  }<o:p></o:p>

  public void debugMsg(String msg) { System.out.println(msg); }<o:p></o:p>

}

正如你所看到的,这个非常简单的类包括一个类型变量,一个类型常量,两个方法和一个构造函数。要使用这个类,只需简单地获取其实例,检查debug是否被启动,并调用debugMsg,如下所示:

...

Logger myLogger = Logger.getInstance();<o:p></o:p>

...<o:p></o:p>

if (Logger.CAN_DEBUG) {<o:p></o:p>

   myLogger.debugMsg("some debug message");<o:p></o:p>

}<o:p></o:p>

设想Logger.CAN_DEBUGfalse。当创建应用程序的时候,将会排除死代码同时不会有由此而产生的字节代码。这是因为编译器知道final static变量Logger.CAN_DEBUG总是为false。如果Logger.CAN_DEBUGtrue,那么代码将会被编译并产生相应的字节代码。这样一来,开启了调试消息的编译将会导致产生更多的字节代码。

这种方法可以扩展到允许对所产生信息的更细微的处理。例如,可以声明一个新的static final的布尔型变量CAN_INFO,还可以实现一个新的public void infoMsg(String msg)方法。

从性能的角度来看,这是可以使用的最佳方法。几种不同的版本可以相互协作以反映所支持的不同层次的消息。例如,你可以发布一个产品版本和一个调试版本。如果在产品版本中出现了问题,则可以用调试版本与其交换以查明问题出现的所在。

这种方法的主要缺点在于无法在运行时进行设置,例如将其作为一个系统属性。

大多数日志记录机制中,主要的性能影响因素在于String对象的创建。这样的话,我们的目标应该是使这种开销达到最小。因此解决方法中需要包括StringBuffer。程序清单4中的Logger类提供了一种可配置的日志记录级别。

<o:p> </o:p>

清单4:提供可配置日志记录级别Logger

public class Logger {<o:p></o:p>

  ...<o:p></o:p>

  // 信息消息的控制器<o:p></o:p>

  public static final int CAN_INFO = 1;<o:p></o:p>

  // 调试消息的控制器<o:p></o:p>

  public static final int CAN_DEBUG = 2;<o:p></o:p>

  // 调试级别——缺省为信息消息<o:p></o:p>

  public int LOG_LEVEL = 1;<o:p></o:p>

  ...<o:p></o:p>

  public void setLogLevel(int level) {<o:p></o:p>

    if(level >= 0) { LOG_LEVEL = level; }<o:p></o:p>

  }<o:p></o:p>

  /** 如果CAN_INFO位被设置则返回true */<o:p></o:p>

  public boolean canInfo() {<o:p></o:p>

    if((LOG_LEVEL & CAN_INFO) == CAN_INFO) { return true; }<o:p></o:p>

    return false;<o:p></o:p>

  }<o:p></o:p>

  /** 如果CAN_DEBUG位被设置则返回true */<o:p></o:p>

  public boolean canDebug() {<o:p></o:p>

    if((LOG_LEVEL & CAN_DEBUG) == CAN_DEBUG) { return true; }<o:p></o:p>

    return false;<o:p></o:p>

  }<o:p></o:p>

  public void debugMsg(String msg) { System.out.println(msg); }<o:p></o:p>

  public void infoMsg(String msg) { System.out.println(msg); }<o:p></o:p>

}

如上的代码示例提供了一种两级的日志记录方法。它可以处理调试消息和信息消息,还可以很容易地扩展到处理更多的类型。这个类为日志记录机制提供了坚实的基础。

在应用程序中使用这种实现方法有两种选项。第一是创建一个实现简单API的基类,应用程序将对其进行扩展。第二是由应用程序实现一个定义了简单API的接口。下面是接口的示例:

public interface LogAPI {<o:p></o:p>

   public void createMsg();<o:p></o:p>

   public void appendLog(String str);<o:p></o:p>

   public void appendLog(int i);<o:p></o:p>

   public void logDebugMsg();<o:p></o:p>

   public void logInfoMsg();<o:p></o:p>

}<o:p></o:p>

TestLogger.java(程序清单5)中提供了这个接口的一个实现。

<o:p> </o:p>

清单5TestLogger.java—定义简单API的接口的示例实现

/* Copyright (c) 2000 Stepping Stone Software Ltd, John Keyes */

public class TestLogger implements LogAPI {

  static Logger myLogger = Logger.getInstance();

  StringBuffer msg = new StringBuffer();

  public static void main(String args[]) {

    String strLevel = System.getProperty("app.loglevel");

    if(strLevel != null) {

    int level = Integer.parseInt(strLevel);

    myLogger.setLogLevel(level);

    }

    TestLogger testLog = new TestLogger();

    testLog.test();

  }

  TestLogger() { }

  public void test() {

    int age = 24;

    String name = "Joe";

    if(myLogger.canDebug()) {

      createMsg();

      appendLog(" DEBUG\n Name:");

      appendLog(name);

      appendLog(" Age:");

      appendLog(age);

      logDebugMsg();

    }

    if(myLogger.canInfo()) {

      createMsg();

      appendLog(" INFO\n Name:");

      appendLog(name);

      appendLog(" Age:");

      appendLog(age);

      logInfoMsg();

    }

  }

 

  public void createMsg() { msg.setLength(0); }

  public void appendLog(String str) { msg.append(str); }

  public void appendLog(int i) { msg.append(i); }

<o:p> </o:p>

  public void logDebugMsg() {

    myLogger.debugMsg(msg.toString());

  }

  public void logInfoMsg() {

    myLogger.infoMsg(msg.toString());

  }

}

StringBuffer对象的重用是此处的关键。通常你会编写如下的代码作为调试消息:

debugMsg("Name:" + name + " Age:" + age);<o:p></o:p>

如前文中所讨论过的,String类型的创建对性能产生不利的影响。如果像TestLogger.java中所示的那样重写,那么获得的性能提升将会是明显的。

日志记录的级别现在可以使用Logger类中定义的setLogLevel方法在运行时定制。在系统属性的帮助下完成这项工作是一个好主意。你必须定义自己的属性;在本例中它被命名为“app.loglevel”。如果所讨论的问题是一个应用程序,那么你可以对JVM使用-D开关设置“app.loglevel”属性[3]。例如:

java –D app.loglevel=3 myApp

另一方面,如果你的程序是一个applet,可以使用<PARAM>标签进行设置:

<PARAM NAME="app.loglevel" VALUE="2">

于是,为了设置日志记录级别,你所要做的一切就是获取属性值并对结果调用SetLogLevel方法:

String strLevel = System.getProperty("app.loglevel");<o:p></o:p>

If(strLevel != null) {<o:p></o:p>

   int level = Integer.parseInt(strLevel);<o:p></o:p>

   Logger.getInstance().setLogLevel(level);<o:p></o:p>

}<o:p></o:p>

这种方法的好处在于:

l         减少对象创建,也就是说,对象被重用

l &

分享到:
评论

相关推荐

    提高 Java 代码性能的各种技巧.docx

    提高 Java 代码性能的各种技巧 在 Java 6, 7, 8 中,String.intern() 方法是一个重要的性能优化技术之一。字符串池是 Java 中的一个重要概念,它可以通过使用唯一的共享 String 对象来使用相同的值不同的地址表示...

    提高java代码性能各种技巧

    Java 代码性能优化技巧总结 Java 代码性能优化是每个 Java 开发者都需要掌握的重要技能。以下是对 Java 代码性能优化的各种技巧的总结: 字符串池的实现 在 Java 中,字符串池(String Intern Pool)是通过使用...

    提高 Java 代码的性能

    总的来说,提高Java代码性能的一个策略是关注尾递归的优化,尤其是当递归深度较大时。开发者需要理解其JVM的行为,利用JIT编译器的动态优化能力,同时注意在编写递归代码时确保正确性和效率。此外,还可以通过其他...

    java代码性能问题检查计划及方案

    Java代码性能问题检查计划及方案 在Java编程中,性能问题往往是开发者关注的重点。虽然Java语言自身具有高效、跨平台的特性,但程序的运行效率却更多地取决于开发者的设计和编码习惯。本方案旨在提供一个详细的检查...

    JAVA代码优化工具

    Java代码优化是提升应用程序性能的关键步骤,尤其是在大型企业级应用或者高性能服务中。优化能够减少内存消耗,提高程序运行速度,降低CPU使用率,并改善整体的用户体验。在Java开发领域,有多种工具可以帮助开发者...

    高性能java代码优化建议

    "高性能java代码优化建议"这一主题涵盖了众多资深开发者积累的实践经验,旨在帮助开发者编写出更高效、性能更强的代码。以下是一些关键的知识点: 1. **避免过度使用同步**:在多线程环境中,过多的同步可能导致...

    代码性能Java比较

    本主题“代码性能Java比较”将深入探讨Java代码性能的各种方面,结合二十三种设计模式的实践应用,帮助开发者提升编程技巧。 首先,我们来谈谈Java代码性能的优化。Java性能优化涵盖了许多领域,如内存管理、线程...

    JAVA敏捷开发 提高java代码的性能

    这不是JAVA的入门书,JAVA敏捷开发教大家如何使自己代码更优化,性能更强大。

    Java程序性能优化 让你的Java程序更快、更稳定附本书示例代码(清晰版)

    总的来说,“Java程序性能优化 让你的Java程序更快、更稳定”这本书将涵盖以上诸多方面,通过理论结合实际的示例代码,帮助读者深入理解Java性能优化的各个方面,从而写出更快、更稳定的Java程序。书中附带的源文件...

    JAVA编码习惯和几款JAVA性能分析工具

    在软件开发中,尤其是使用Java语言时,遵循良好的编码习惯对于编写可读性高、易于维护的代码至关重要。以下是一些关键的Java编码习惯: 1. **Eclipse源代码格式化**:Eclipse作为流行的Java IDE,提供了自动格式化...

    基于JavaParser的代码调用链分析,可以用于分析Java代码的方法调用链.zip

    JavaParser是一个强大的开源库,主要用于解析、操作和生成Java源代码。这个压缩包中的内容可能包含了一系列使用JavaParser...通过深入学习和应用JavaParser,我们可以更好地理解和优化Java代码,提升软件的质量和性能。

    四种常用的java代码扫描工具介绍

    PMD 是一个开源的静态代码分析工具,能够检查 Java 代码中的编码风格、命名约定、安全性、性能等方面的缺陷。PMD 提供了大量的规则和配置项,能够满足不同的需求和标准。 Jtest 是一个商业的静态代码分析工具,能够...

    如何提高Java的性能和执行效率

    提高Java性能的方法主要包括以下几个方面: 1. 程序设计:良好的设计是性能优化的基础。采用合适的设计模式和方法可以有效利用资源,如内存、CPU、缓存和多线程,构建高性能、高可扩展性的系统。设计阶段就应考虑到...

    35个Java代码性能优化总结.pdf

    ### Java代码性能优化总结 Java代码性能优化是一个广泛的领域,涉及多个方面,包括但不限于内存使用、循环效率、算法优化、集合框架使用等。本总结旨在汇总35个常见的Java代码性能优化技巧。 #### 1. 使用final...

    java程序性能优化

    第3章从代码层面介绍如何编写高性能的Java程序。第4章介绍了并行开发和如何通过多线程提高系统性能。第5章立足于JVM虚拟机层面,介绍如何通过设置合理的JVM参数提升Java程序的性能。第6章为工具篇,介绍了获取和监控...

    《Java程序性能优化》(葛一鸣)PDF版本下载.txt

    《Java程序性能优化》这本书主要探讨了如何通过各种技术和策略来提高Java应用程序的性能。 ### 性能瓶颈分析 - **CPU使用率高**:程序执行时,某些部分可能过度消耗CPU资源,导致性能下降。 - **内存泄漏**:对象...

    Java程序性能优化

    第3章从代码层面介绍如何编写高性能的Java程序。第4章介绍了并行开发和如何通过多线程提高系统性能。第5章立足于JVM虚拟机层面,介绍如何通过设置合理的JVM参数提升Java程序的性能。第6章为工具篇,介绍了获取和监控...

    java 性能权威指南

    在代码层面,书中会讲解如何编写高效且无副作用的Java代码,包括避免过度使用同步、合理使用数据结构、优化循环和减少不必要的对象创建等。这些实践原则有助于编写出更健壮、性能更佳的程序。 另外,书中还涵盖了...

Global site tag (gtag.js) - Google Analytics