5.5 Stack-based Execution and Exception Propagation

An exception in Java is a signal that indicates the occurrence of some important or unexpected condition during execution. For example, a requested file cannot be found, or an array index is out of bounds, or a network link failed. Explicit checks in the code for such conditions can easily result in incomprehensible code. Java provides an exception handling mechanism for systematically dealing with such error conditions.

The exception mechanism is built around the throw-and-catch paradigm. To throw an exception is to signal that an unexpected error condition has occurred. To catch an exception is to take appropriate action to deal with the exception. An exception is caught by an exception handler, and the exception need not be caught in the same context that it was thrown in. The runtime behavior of the program determines which exceptions are thrown and how they are caught. The throw-and-catch principle is embedded in the try-catch-finally construct.

Several threads can be executing in the JVM (see Chapter 9). Each thread has its own runtime stack (also called the call stack or the invocation stack) that is used to handle execution of methods. Each element on the stack (called an activation record or a stack frame) corresponds to a method call. Each new call results in a new activation record being pushed on the stack, which stores all the pertinent information such as storage for the local variables. The method with the activation record on top of the stack is the one currently executing. When this method finishes executing, its record is popped from the stack. Execution then continues in the method corresponding to the activation record which is now uncovered on top of the stack. The methods on the stack are said to be active, as their execution has not completed. At any given time, the active methods on a runtime stack comprise what is called the stack trace of a thread's execution.

Example 5.9 is a simple program to illustrate method execution. It calculates the average for a list of integers, given the sum of all the integers and the number of integers. It uses three methods:

  • The method main() which calls the method printAverage() with parameters giving the total sum of the integers and the total number of integers, (1).

  • The method printAverage() in its turn calls the method computeAverage(), (3).

  • The method computeAverage() uses integer division to calculate the average and returns the result, (7).

Example 5.9 Method Execution
public class Average1 {

    public static void main(String[] args) {
        printAverage(100,0);                                  // (1)
        System.out.println("Exit main().");                   // (2)

    public static void printAverage(int totalSum, int totalNumber) {
        int average = computeAverage(totalSum, totalNumber);  // (3)
        System.out.println("Average = " +                     // (4)
            totalSum + " / " + totalNumber + " = " + average);
        System.out.println("Exit printAverage().");           // (5)

    public static int computeAverage(int sum, int number) {
        System.out.println("Computing average.");             // (6)
        return sum/number;                                    // (7)

Output of program execution:

Computing average.
Average = 100 / 20 = 5
Exit printAverage().
Exit main().

Execution of Example 5.9 is illustrated in Figure 5.6. Each method execution is shown as a box with the local variables. The box height indicates how long a method is active. Before the call to the method System.out.println() at (6) in Figure 5.6, the stack trace comprises of the three active methods: main(), printAverage() and computeAverage(). The result 5 from the method computeAverage() is returned at (7) in Figure 5.6. The output from the program is in correspondence with the sequence of method calls in Figure 5.6.

Figure 5.6. Method Execution


If the method call at (1) in Example 5.9

printAverage(100, 20);                                // (1)

is replaced with

printAverage(100,  0);                                // (1)

and the program is run again, the output is as follows:

Computing average.
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at Average1.computeAverage(Average1.java:18)
        at Average1.printAverage(Average1.java:10)
        at Average1.main(Average1.java:5)

Figure 5.7 illustrates the program execution. All goes well until the return statement at (7) in the method computeAverage() is executed. An error condition occurs in calculating the expression sum/number, because integer division by 0 is an illegal operation. This error condition is signalled by the JVM by throwing an ArithmeticException (see "Exception Types" on page 185). This exception is propagated by the JVM through the runtime stack as explained on the next page.

Figure 5.7. Exception Propagation


Figure 5.7 illustrates the case where an exception is thrown and the program does not take any explicit action to deal with the exception. In Figure 5.7, execution of the computeAverage() method is stopped at the point where the exception is thrown. The execution of the return statement at (7) never gets completed. Since this method does not have any code to deal with the exception, its execution is likewise terminated abruptly and its activation record popped. We say that the method completes abruptly. The exception is then offered to the method whose activation is now on top of the stack (method printAverage()). This method does not have any code to deal with the exception either, so its execution completes abruptly. Lines (4) and (5) in the method printAverage() never get executed. The exception now propagates to the last active method (method main()). This does not deal with the exception either. The main() method also completes abruptly. Line (2) in the main() method never gets executed. Since the exception is not caught by any of the active methods, it is dealt with by the main thread's default exception handler. The default exception handler usually prints the name of the exception, with an explanatory message, followed by a printout of the stack trace at the time the exception was thrown. An uncaught exception results in the death of the thread in which the exception occurred.

If an exception is thrown during the evaluation of the left-hand operand of a binary expression, then the right operand is not evaluated. Similarly if an exception is thrown during the evaluation of a list of expressions (for example, a list of actual parameters in a method call), then evaluation of the rest of the list is skipped.

If the line numbers in the stack trace are not printed in the output as shown previously, it is advisable to turn off the JIT (Just-in-Time) compilation feature of the JVM in the Java 2 SDK:

>java -Djava.compiler=NONE Average1