Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

Tips and tricks for better Java performance

Kelby Zorgdrager, Sr. Software Engineer, Sun Microsystems Inc.
Kelby Zorgdrager is a Sr. Software Engineer at Sun Microsystems Inc. Prior to working as a Software Engineer, Kelby was a Sr. Java Instructor for Sun Educational Services where he provided training and consulting services to over 2500 students. Kelby has presented at major Java trade shows including JavaOne, Java Business Expo, and COMDEX. Kelby has worked with, and developed in, the Java language since January of 1996.

Summary:  Ever since the Java language was released in 1995, many different people, from developers to CEOs, have expressed their concern about its performance. I have heard, and even seen, people choose less appropriate technologies for a solution because of these concerns. Many of the performance concerns are quite valid. In fact, there was (and still is) so much focus about Java's performance that Sun and many of its partners have been working very diligently on ways to increase it. It is rumored that different virtual machine vendors use execution speeds of native applications as the benchmark to reach. Since that day seems a long way off, it is up to the developer to create applications that provide the best possible performance.

Date:  01 May 2001
Level:  Intermediate

Activity:  2899 views
Comments:  

With the creation of what is referred to as "Internet-time," the time-to-market (or development time) of an application has become very important. A one year development cycle could lose a company's established market share. Even a six month development cycle could be detrimental to a company's continued success. As a result, it is possible that during a rapid application development cycle, "best practices" are ignored. Due to time constraints, developers may create code without weighing the outcome of the implementation they choose. In many cases, this translates into buggy code. It also translates into a more costly development cycle as the maintenance phase requires more effort and lasts longer. Unfortunately, rapid application development can also translate into a lower solution performance than what should have been obtained.

There could be many reasons why performance, or lack thereof, is a direct result of this "Internet-time" development cycle. In some cases, the developers working on the solution are inexperienced with the problem domain. In other cases, the developers are unfamiliar or inexperienced with the tools they are using. The latter could be more detrimental than the former. Despite the technical recruiter's attempts of equality, Microsoft's Visual Basic® and Java are two different programming languages. Likewise, C++ and Java are two different programming languages. Therefore, someone experienced writing solutions using only Visual Basic is more likely to create a Java solution that performs slower than a developer who has been working with the Java language for four years. This does not mean that the Visual Basic-made Java developer is less of a programmer than the Java developer. Nor does it mean that the Java developer is a better programmer than the Visual Basic developer. It could just mean that the Java developer understands and has more experience with the tool he/she is using to create a solution.

One must realize that even the most experienced Java developers are not perfect. In fact, if a developer learns poor habits early, and continues to follow those habits, the resulting solution could be worse than that of the Visual Basic-made Java developer. Automatic memory allocation and deallocation, one of the many advantages of the Java language, could also be one of its biggest disadvantages. Having a language with automatic memory allocation and deallocation allows developers to become lazy. Unfortunately, this laziness can cause poor performance. Likewise, dynamic class loading, though it provides an extreme amount of flexibility, can also be a disadvantage. The design and implementation of a class can adversely affect the execution performance (as you will see below). The Java language provides many features which lessen the development effort required to create a solution. However, many of these same features, due to their automatic behaviors, can adversely affect the performance of the solution. If developers abuse these features, intentionally or unintentionally, the performance of the solution can be affected. Therefore, it is extremely important to be conscientious of the implementation you choose.

Hopefully by reading this article, you will learn (or re-learn) some important tricks that you can incorporate into your solution to enhance performance. These tricks alone will not deliver the promise of native application execution speeds. They may improve performance enough to brighten your end-user's experience. It is important to realize that performance of your application is a direct result of many different factors, from processor speed to the amount of random access memory you have. When considering performance in the context of a Java solution, it is imperative to factor in the performance of the virtual machine itself. You will find that different virtual machines (including major and minor revisions and vendors) have different performance. Therefore, I suggest analyzing the performance of your virtual machine as part of your overall performance evaluation. At the end of the article, you will find a list of resources where you can obtain Virtual Machine performance data.

This article also presents both coding tricks and design tips to better performance. It is important to realize that whenever you embark on an optimization journey of an existing solution you should use a profiling tool. I highly suggest fixing the areas of question outlined by a tool prior to blanketed, hack-like attempts to optimize your code. Also, it is worthwhile noting that writing a solution for good performance may have tradeoffs, from design to maintenance. It is up to you as a developer to discern which tradeoffs are more important. This article does not try to teach you how to take an existing application and make it perform better. Rather, it presents some "things to think about" while developing a Java solution.

Execution speed can be a direct result of the implementation you choose to provide some desired functionality. In some cases, the implementation you choose is the best possible implementation in terms of performance. In other cases, a "lazy" implementation may yield poor performance. A common design for a class that contains multiple constructors is to have a constructor use another constructor, which uses another constructor to provide the initialization. Though this makes development and maintenance easier, it may hurt performance. For example, take the code listing below. The listing contains three different ways to implement a class called SlowFlow, one of which could yield slower execution than the other two. In all three cases, SlowFlow has three constructors, which initialize the object. However, the execution flow of the initialization does not follow the same path. For example, the execution flow of the first column has more overhead than the execution of the third column, as the first column relies on two additional method invocations. The second column is better than the first as we have reduced the number of method invocations required for the object initialization.

Listing 1: Object initialization constructor design
Column 1Column 2Column 3
class SlowFlow
{
 private int someX, someY;
 SlowFlow()
 {
  this(777); 
 }

 SlowFlow(int x)
 {
   this(x,778);
 }
 
 SlowFlow(int x, int y)
 {
   someX = x;
   someY = y;
 }
}

class SlowFlow
{
 private int someX, someY;

 SlowFlow()
 {
   this(777,778);
 }

 SlowFlow(int x)
 {
   this(x,778);
 }

 SlowFlow(int x, int y)
 {
   someX = x;
   someY = y;
 }
}

class SlowFlow
{
 private int someX, someY;

 SlowFlow()
 {
   someX = 777;
   someY = 778;
 }

 SlowFlow(int x)
 {
   someX = x;
   someY = 778;
 }

 SlowFlow(int x, int y)
 {
   someX = x;
   someY = y;
 }
}

As you can see, the result of each implementation is the same, someX and someY are initialized with the values of x and y passed into the third constructor. However, the paths taken to reach the initialization are different. In the first column, calling SlowFlow() to create a SlowFlow object, calls SlowFlow(int x), which in turn calls SlowFlow(int x, int y). SlowFlow()'s execution does not return until SlowFlow(int x)'s execution returns which can't return until SlowFlow(int x, int y) returns. This flow could adversely affect the amount of time required to create the object. The second column presents a better solution, however, as it relies on another method for the initialization. The last implementation of SlowFlow (3rd column) performs the actual object initialization in each constructor, or in a single method call. This may result in faster object creation speeds. Realize there is a tradeoff involved; the maintenance of the code in the third column may be more intensive as the maintenance may need to occur in every constructor. This type of design, or optimization technique, is normally referred to as code in-lining or method in-lining. Code in-lining is a useful technique as it saves execution overhead by lowering the number of method calls on the execution stack. Code in-lining is useful in both constructor and method implementations. Some Java compilers will perform this functionality for you and some may not. Others may perform code in-lining of methods but not constructors. Therefore, it is important to know what optimization functionality your compiler provides.

Another important aspect of performance deals with the amount of time required to "install" a class into a virtual machine. The Java language utilizes a dynamic, run-time binding enabling solutions to dynamically install classes, as needed, over time. This type of model is probably most common in the Java applet solution scenario. Though network class loading is the most apparent in applet solutions, it is also utilized in Remote Method Invocation (RMI) and Jini solutions. It can also be used in application as well as enterprise solutions like servlets and Enterprise JavaBeans. In the applet architecture, the classes used to run the applet are normally loaded from a remote Web server. Remember all of the "system" classes are loaded from the local JVM library, but the implementation of the applet class and its associated classes are loaded via the network. Therefore, the number of resources and the size of the resources that have to be downloaded over the network can effect the performance of the applet.

A common solution to decrease the amount of time required to download the appropriate resources is to use a Java Archive (JAR). A JAR is a collection of resources compressed into a single file. Using a JAR allows the recipient to make a single socket connection and transfer a single file to retrieve all of the resources. Without the use of a JAR, the resources are normally retrieved on a one-by-one basis, possibly reusing the same socket connection. As transfer speeds become less and less of an issue, the amount of time required to load a class, or a set of classes, from the network will lessen. However, until everyone has a high-speed connection, download times should be considered. Creating an archive that contains tons of data in a compressed form definitely lessens the latency. However, relying on JAR to compress your classes should not be a reason to create classes that don't compile into the smallest possible footprint. The smaller the class, the less time required to load it into memory. Code in-lining not only enhances execution speeds, but also reduces the compiled size of a class. Using the listing above, the compiled versions of each implementation go from biggest (on the left) to smallest (on the right). In the context of these three implementations, the difference is a couple hundred bytes, which may not seem like a lot. However, if your application loads 10 classes that utilize code in-lining, you could save a few kilobytes. If it loads 100 classes, you could save a megabyte. Class size is an important thing to remember when dealing with optimization and performance, especially if you are using network class loading.

On that note, another way to minimize class size is to extend an existing class rather than implement an interface. This may not always be possible nor desirable, but as with the in-lining above, you have to weigh the tradeoffs. Extending a class allows you to override the methods of your desire. Implementing an interface requires you to implement all of the methods, even if you are only interested in one or two. If there is an "abstract" class that provides a default implementation for all of the methods in an interface, you may find that it is better to extend that "abstract" class and override the desired behaviors.

The code listing below provides two classes that handle window closings. The listing on top extends the WindowAdapter class as the "abstract" class. The listing on the bottom implements the WindowListener interface. If you look at both implementations, they provide the same functionality; they handle WindowEvents . In particular, they handle WindowEvents of a window that is closing.

Listing 2: "extends" versus "implements"
//extends...

import java.awt.event.*;
import java.awt.*;

public class MyWindowAdapter extends WindowAdapter 
{  
  public void windowClosing(WindowEvent we)
  {    
    Container source = (Container) we.getSource(); 
        source.setVisible(false);  
  }
}

//implements...

import java.awt.event.*;
import java.awt.*;

public class MyWindowListener
             implements WindowListener
{  

  public void windowClosing(WindowEvent we)
  { 
    Container source = (Container)      
        we.getSource(); 
        source.setVisible(false);
  }
  
  public void windowClosed(WindowEvent we)
  {}
  
  public void windowActivated(WindowEvent we)
  {}
  
  public void windowDeactivated(WindowEvent we)
  {}
  
  public void windowIconified(WindowEvent we)
  {}
  
  public void windowDeiconified(WindowEvent we)
  {}
  
  public void windowOpened(WindowEvent we)
  {}
}

MyWindowListener , despite its desired behavior to only implement windowClosing(WindowEvent) , provides an implementation for every single method declared in the WindowListener interface. Even though the implementations of these methods are empty, they are contained in the compiled version of this class (otherwise the class would not be a WindowListener ). This places some overhead on the class size, whereas MyWindowAdapter only contains an implementation of windowClosing(WindowEvent) . Since the other WindowListener methods are inherited, they are not included in the compiled version of class. This allows the class size of MyWindowAdapter to be smaller than MyWindowListener . In fact, MyWindowAdapter has a class size of 476 bytes; whereas MyWindowListener has a class size of 843 bytes. I will admit we are only talking 400 bytes here, but remember the smaller the class, the less time to load! If there were 10 classes in a given solution that had the opportunity to inherit instead of implement, you could save a couple kilobytes. In terms of network class loading, this could save your end user a second or two of time.

There are other cases beyond event listeners where extending may make more sense than implementing or even creating your own implementation. For example, if you want to create a class that provides linked list functionality, from a class size perspective, it may be better for you to extend java.util.LinkedList instead of writing your own from scratch. Likewise, if you want to change the way a JButton is drawn, you might find that it is better to extend an existing ButtonUI class (like javax.swing.plaf.metal.MetalButtonUI ) than create your own. There are two direct results of extending a class versus implementing the interface. One result is the smaller class size (which as noted above could lower the amount of time required to load the class). The other result is the fact that if you extend a "system" class, the inherited functionality will be loaded locally, again saving time.

Now that we have discussed some design tips, let's look at some tricks you can use to improve the performance of your solution.

Just about every Java solution uses streams to read and write data. The process of reading and writing data can be extremely costly in terms of performance. Since the I/O is normally provided through the use of some native library layered beneath Java programming, there is some default overhead incurred. However, the type of stream you choose can also affect performance. The Java language provides two types of streams: Readers and Writers and Input and Output streams. Readers and Writers are great for reading and writing data at a higher, more abstracted level. For example, Readers and Writers are great for reading and writing String data. Input and Output streams provide data access mechanisms at a much lower level, the byte level. In either case, if your solution demands fast consecutive reads and/or writes, you might consider using the buffered stream permutation. Buffered streams make use of an internal data buffer. This allows I/O operations, such as reading data, to perform better. In the context of reading, upon the first read, the internal buffer is "filled" with data. Each consecutive read is read from the buffer until the buffer is empty, at which point the read will cause the buffer to fill again. Realize there is a memory implication when you use buffered streams. The default buffer size for both input and output is 2048 bytes. You can adjust the size of the buffer by calling the appropriate constructor. It is important to realize that the buffer is held in RAM. Therefore, a 30 kilobyte buffer may not be desirable as it limits the amount of RAM the JVM can use.

Object comparison can be another costly operation. Whenever possible, try to compare objects based on the reference value instead of their actual value. In the context of Java code, try to compare objects using = = instead of equals() . Though this is not always possible, the object evaluation using = = is faster as two integers are equated. Every Java class has the ability to define its own equals() method. If you want to compare your objects using equals() , provide an implementation of equals() in the class. However, providing your own equals() method does not guarantee better performance over the inherited or default equals() method. If you do provide an equals() method, try to avoid comparing member variables based on their String representation. It sounds silly, but I have seen it quite a few times. A String comparison (using equals() ) checks to see if the reference value of each String is the same. Assuming they are different, a character-by-character comparison is used to compute equality. Taking the two words "kelby zorgdrager" and "kelby zorgdragor", this character-by-character evaluation could be costly as the first 14 characters are the same. Not until the comparison reached the 15 character, would it "realize" the two Strings were dissimilar. When writing your own equals() method, consider using this flow:

  • Check to see if the object references are the same.
  • If they are not the same, check the internal member's values.

One of greatest advantages Java programming has (over C++) is its automatic memory allocation and deallocation provided through the garbage collector. A common tendency (and pitfall) as a Java developer is to abuse the garbage collector and the functionality it provides. Just because you, as a developer, don't have to malloc and free memory, does not mean this operation doesn't occur. It does occur; but it occurs behind the scenes. Allocating and deallocating memory can be expensive in terms of execution speed and system resources. Haphazardly creating temporary objects on any platform can seriously affect the performance of an application. Reaching the "free" operation in Java programming could be time consuming, as the garbage collector needs to determine if it can collect a given reference and its associated space. In fact, the garbage collector runs as a background thread and "runs" only when there is a free cycle. This means that your application could be consuming more memory than needed while it waits for the garbage collector to run. Therefore, make every attempt to be conscious of when memory is being allocated and when it goes out of scope. You may even want to consider "nulling" out objects when you are done using them. Not only does this make your code more explicit, it also "helps" the garbage collector as the reference validity determination process is lessened. Since there are different garbage collection algorithms found in different implementations of JVMs, you many want to explore the garbage collection performance of different VMs.

There are a couple of other tips and tricks you should consider when developing Java solutions:

  1. Use collections wisely.
    In many cases, collections are represented as arrays behind the scenes. For example, if you add 30 objects to a collection and then remove 25 of the 30 objects, your collection will minimally be an array 30 elements long. If you are not going to use the other 25 "spaces" in the collection, you may want to consider trimming it. java.util.Vector has a trimToSize() method that does just that. Trimming can be detrimental to performance if the collection frequently grows in size. You may also want to consider setting the "growth" factor of the collection you are using. Vector's growth factor doubles it size when it runs out of space; the exponential growth could definitely cause you to lose free memory very quickly.
  2. Reuse objects.
    A solution may require you to create 20 instances of a Foo over its execution lifetime. Creation of objects can be a costly operation (in terms of memory allocation and constructor execution). Therefore, you should consider reusing objects as much as possible. There are object-oriented design patterns that address object reuse without having to create a new object. A common strategy is to have an init() method that performs the initialization of an object. This allows you to create the object, use it, reinitialize it, and then reuse it.
  3. Reuse threads.
    As noted above, it is costly to create a new object. Creating a new thread object is even more costly than creating a new object (as there are system resources tied to a thread). Therefore, try to reuse threads as much as possible. Commonly thread reuse is obtained through the use of a thread pool in your solution.
  4. Be kind when redrawing.
    Painting and repainting a user interface in Java programming can be a slow operation, especially if the entire graphics area is redrawn. Therefore, when performing intensive drawing operations, consider repainting only the "dirty" area. You may also want to consider using a backing-store (off-screen buffer) to set up your drawing and then paint the backing-store's image.

Despite the many advantages of the Java language, there are some inherent disadvantages (though many of the disadvantages are advantages that have been abused). The disadvantages are quite evident when the performance of a Java solution is examined. There will always be areas where developers can improve their implementation to improve the performance of a solution. Hopefully, this article provided some insight on where and how to get started. I challenge you to be mindful of the implications your design and implementation have on performance and memory usage. Remember to always integrate the use of a profiling tool in your attempts to increase performance and lower your memory footprint.


Resources

About the author

Kelby Zorgdrager is a Sr. Software Engineer at Sun Microsystems Inc. Prior to working as a Software Engineer, Kelby was a Sr. Java Instructor for Sun Educational Services where he provided training and consulting services to over 2500 students. Kelby has presented at major Java trade shows including JavaOne, Java Business Expo, and COMDEX. Kelby has worked with, and developed in, the Java language since January of 1996.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Sample IT projects
ArticleID=10135
ArticleTitle=Tips and tricks for better Java performance
publish-date=05012001
author1-email=
author1-email-cc=

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

For articles in technology zones (such as Java technology, Linux, Open source, XML), Popular tags shows the top tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), Popular tags shows the top tags for just that product zone.

For articles in technology zones (such as Java technology, Linux, Open source, XML), My tags shows your tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), My tags shows your tags for just that product zone.

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).