7.1 Stack-Based Execution and Exception Propagation
The exception mechanism is built around the throw-and-catch paradigm. To throw an exception is to signal that an unexpected event 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 in which it was thrown. 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 (p. 375).
Several threads can be executing at the same time in the JVM (§22.2, p. 1369). Each thread has its own JVM stack (also called a runtime stack, call stack, or invocation stack in the literature) that is used to handle execution of methods. Each element on the stack is called an activation frame or a stack frame and corresponds to a method call. Each new method call results in a new activation frame being pushed on the stack, which stores all the pertinent information such as the local variables. The method with the activation frame on the top of the stack is the one currently executing. When this method finishes executing, its activation frame is popped from the top of the stack. Execution then continues in the method corresponding to the activation frame that is now uncovered on the 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 JVM stack make up what is called the stack trace of a thread’s execution.
Example 7.1 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() calls the method printAverage() with parameters supplying the total sum of the integers and the total number of integers, (1).
- The method printAverage() in turn calls the method computeAverage(), (3).
- The method computeAverage() uses integer division to calculate the average and returns the result, (7).
Example 7.1 Method Execution
public class Average1 {
public static void main(String[] args) {
printAverage(100, 20); // (1)
System.out.println(“Exit main().”); // (2)
}
public static void printAverage(int totalSum, int totalCount) {
int average = computeAverage(totalSum, totalCount); // (3)
System.out.println(“Average = ” + // (4)
totalSum + ” / ” + totalCount + ” = ” + average);
System.out.println(“Exit printAverage().”); // (5)
}
public static int computeAverage(int sum, int count) {
System.out.println(“Computing average.”); // (6)
return sum/count; // (7)
}
}
Output of program execution:
Computing average.
Average = 100 / 20 = 5
Exit printAverage().
Exit main().
Execution of Example 7.1 is illustrated in Figure 7.1. Each method execution is shown as a box with the local variables declared in the method. The height of the box indicates how long a method is active. Before the call to the method System.out.println() at (6) in Figure 7.1, the stack trace comprises the three active methods: main(), printAverage(), and computeAverage(). The result 5 from the method computeAverage() is returned at (7) in Figure 7.1. The output from the program corresponds with the sequence of method calls in Figure 7.1. As the program terminates normally, this program behavior is called normal execution.
If the method call at (1) in Example 7.1
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 7.2 illustrates the program execution when the method printAverage() is called with the arguments 100 and 0 at (1). All goes well until the return statement at (7) in the method computeAverage() is executed. An error event occurs in calculating the expression sum/number because integer division by 0 is an illegal operation. This event is signaled by the JVM by throwing an ArithmeticException (p. 372). This exception is propagated by the JVM through the JVM stack as explained next.
Figure 7.1 Normal Method Execution
Figure 7.2 illustrates the case where an exception is thrown and the program does not take any explicit action to deal with the exception. In Figure 7.2, execution of the computeAverage() method is suspended 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 frame popped. We say that the method completes abruptly. The exception is then offered to the method whose activation is now on the top of the stack (printAverage()). This method does not have any code to deal with the exception either, so its execution completes abruptly. The statements at (4) and (5) in the method printAverage() never get executed. The exception now propagates to the last active method (main()). This does not deal with the exception either. The main() method also completes abruptly. The statement at (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, as in this case, results in the death of the thread in which the exception occurred.
Figure 7.2 Exception Propagation
If an exception is thrown during the evaluation of the left-hand operand of a binary expression, then the right-hand operand is not evaluated. Similarly, if an exception is thrown during the evaluation of a list of expressions (e.g., a list of actual parameters in a method call), 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, use the following command to run the program:
Click here to view code image >
java -Djava.compiler=NONE Average1