Skip to main content

skip to main content

developerWorks  >  Java technology  >

Eye on performance: Exceptions to exceptions

Understanding the real costs

developerWorks
Document options

Document options requiring JavaScript are not displayed


Rate this page

Help us improve this content


Level: Introductory

Jack Shirazi (jack@JavaPerformanceTuning.com), Director, JavaPerformanceTuning.com
Kirk Pepperdine (kirk@JavaPerformanceTuning.com), CTO, JavaPerformanceTuning.com

10 Feb 2004

Java 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.



Back to top


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.



Back to top


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.




Rate this page


Please take a moment to complete this form to help us better serve you.



YesNoDon't know
 


 


12345
Not
useful
Extremely
useful
 


Back to top