IBM Skip to main content
Search for:   within 
      Search help  
     IBM home  |  Products & services  |  Support & downloads   |  My account

developerWorks > Java technology
developerWorks
Eye on performance: Exceptions to exceptions
71 KBe-mail it!
Contents:
Coding crossroads: Like this, or like that?
Dynamic tuning
The final word
Resources
About the authors
Rate this article
Related content:
Eye on performance series
Best practices in EJB exception handling
Build a better exception handling framework
IBM Developer Kits for the Java platform (downloads)
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
Understanding the real costs

Level: Introductory

Jack Shirazi (mailto:jack@JavaPerformanceTuning.com?cc=&subject=Exceptions to exceptions), Director, JavaPerformanceTuning.com
Kirk Pepperdine (mailto:kirk@JavaPerformanceTuning.com?cc=&subject=Exceptions to exceptions), CTO, JavaPerformanceTuning.com

10 Feb 2004

Column iconJava performance enthusiasts Jack Shirazi and Kirk Pepperdine, Director and CTO of JavaPerformanceTuning.com, follow performance discussions all over the Internet to see what's troubling developers. In this month's stop at the JavaRanch, they counter the campfire stories about exceptions with a detailed look at the story behind the story.

In our first installment of this column, we discussed the cost of throwing exceptions. This month, we revisit the subject from a different point of view -- how the JVM handles thrown exceptions -- and we ponder whether optimal exception coding should be considered a premature optimization or a best practice.

Coding crossroads: Like this, or like that?
Performance discussion groups are full of questions like "should I code like this, which is how everyone usually does it, or should I code like that, for better performance?" Conventional wisdom suggests that we should avoid early optimizations and apply best practices until performance measurement shows the need for optimization, but the reality is that every time we write a line of code, we are making a decision that can affect performance.

One discussion on the JavaRanch examined two alternative methods of assuring type safety -- one throwing an exception, one using instanceof -- and asked the question, "Which approach is better?" Listings 1 and 2 show the two methods.

Listing 1. Using instanceof to branch

    Listing 1: using instanceof to branch
      for (int i = 0; i < max; i++)
      {
         Object obj = myVector.elementAt(i);
         if (obj instanceof MySpecialClass)
         {
            // do this
         }
      }


Listing 2. Throwing an exception to branch

      for (int i = 0; i < max; i++) {
        try {
          MySpecialClass myClass = (MySpecialClass)myVector.elementAt(i);
          // do this
        } catch (ClassCastException cce) {
          continue; // for loop
        }
      }

One of the dangers when asking this type of question is that you can lose a lot of context in trying to boil the question down to a simple example. Not having sufficient context leads to long, often confusing discussions, because as each respondent reads the question, they each bring their own context to the problem. And while all of this extra context can add value, it can often distract us from the question in the original post. With this in mind, let's see if we can filter out some truths from the trail of messages we found in this thread.

Characteristics of an exception
The first thing that most developers will tell you about exceptions is that they are expensive. If you continue to probe as to why they are expensive, the most common answer will probably be that we need to capture the current state of the execution stack. Although this is certainly a strong component of the cost, by listing some of the characteristics of exceptions, we can start to see that this is only the beginning of the story. Here are some of the characteristics of an exception:

  • Can be thrown
  • Can be caught
  • Can be created programmatically
  • Can be created by the JVM
  • Is represented as a first-class object
  • Has a depth of inheritance that starts at 3
  • Is composed of Strings (and StackTraceElements from 1.4)
  • Relies on the native method, fillInStackTrace()

The major differentiator between an exception and any other object is that it can be thrown and caught. Let's start our investigation by examining the course of events that is triggered when an exception is thrown.

The cost of handling an exception
To throw an exception, the JVM issues an athrow bytecode instruction. The athrow instruction causes the JVM to pop the exception object off the top of the execution stack. It then searches the current execution stack frame looking for the first catch clause that can handle an exception of that class, or one of its superclasses. If no catch block is found in the current stack frame, then the current stack frame is released and the exception is re-thrown in the context of the next stack frame, and so on until a stack frame with a suitable catch clause is found, or the bottom of the execution stack is reached. Ultimately, if no appropriate catch block is found, all of the stack frames are released, and the thread is terminated after the ThreadGroup object has been given a chance to handle the exception (see ThreadGroup.uncaughtException). If an appropriate catch block is found, the program counter is reset to the first line of code in that block.

From this description, we can see that handling a thrown exception is quite an expensive proposition. Take another look at the list of exception characteristics above. Note that aside from the fact that the JVM can "spontaneously" create an exception, all of the remaining costs are no different than those incurred during the lifecycle of any other first-class object.

The cost of an exception as a first-class object
Now look back at Listing 2. The exception is thrown only if the cast fails. How does the JVM process this? A checkcast operation is issued whenever an application is required to perform a type cast. This operation does not do anything but check to make sure that the type of the argument on the top of stack is what is expected. If it isn't, then it throws a ClassCastException.

A gentler way to type-check is to use the instanceof operator, as shown in Listing 1. Among the differences between checkcast and instanceof is that the latter leaves a 0 or 1 on the top of the stack to indicate failure or success.

The instanceof operator follows a very strict set of rules to determine success or failure. The rules need to take into account whether or not a variable reference is null, an array, an interface, or just a class. Once the type of variable has been determined, then the hierarchy of the qualifying operand of the other side must be searched until either a match is found or the end of the hierarchy is reached. In the case of an array, the type of the underlying element must undergo the same scrutiny.

In addition to this cost, once you have applied instanceof, you typically cast the object in the subsequent code. Performing a subsequent cast causes the checkcast bytecode to be executed. So, the logic used to determine if the cast will work may be repeated (assuming the JVM has not optimized the extra check away). Even so, because using instanceof operator does not require us to create a new object, it is a much less expensive operation in both memory and execution resources than creating and handling an exception. So, the answer to the original question would appear to be obvious. Or, is it?

A hidden risk
Catching the ClassCastException also has a hidden risk, which is that we might capture ClassCastExceptions thrown from the code that would replace the "do this" comment. If this code were to throw a ClassCastException in the middle of some operation, we would catch it, assume it originated from the cast to MySpecialClass, and silently ignore it, which might leave our application in an inconsistent state.

Dynamic tuning
So far, all we have done is estimated the one-time execution cost of each of the two proposed coding styles. Now we need to understand the conditions under which the code will be executing so that we can determine which coding style should be used.

Consider the case of iterating through a collection to get an idea of the real costs incurred. If the collection were to contain a homogenous set of objects, and if we were to perform an instanceof on every object pulled, the procedure would impose an unnecessary cost on the overall runtime. On the other hand, if the collection were to contain a heterogeneous set of objects, then we would be far better off using the instanceof operation instead of incurring the cost of a storm of exceptions.

This scenario gives us the final clue to the best practice: Exceptions should be reserved for exceptional situations. Using exceptions in exceptional circumstances is ideal for performance; using checks to avoid throwing exceptions in non-exceptional circumstances is ideal for performance.

The final word
From all of this we can see that following best practices (here, that exceptions should be used for exceptional circumstances) can produce the better performance. Sometimes we need to run through considerations like those in this article to determine exactly what best practice is, and to determine what are the performance considerations of that best practice and it's alternatives. But now, instead of worrying about prematurely optimizing code, we end up with a best coding practice which is appropriate independently of performance, and which also provides optimal performance -- truly the best of both worlds.

Resources

About the authors
Jack Shirazi is the Director of JavaPerformanceTuning.com and author of Java Performance Tuning (O'Reilly). In addition to his performance tuning focus, Jack also develops intelligent agent technology.


Kirk Pepperdine is the Chief Technical Officer at Java Performance Tuning.com and has been focused on object technologies and performance tuning for the last 15 years. Kirk is a co-author of the book ANT Developer's Handbook.



71 KBe-mail it!

What do you think of this document?
Killer! (5) Good stuff (4) So-so; not bad (3) Needs work (2) Lame! (1)

Comments?



developerWorks > Java technology
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact