|
Programming for the Java Virtual Machine Authors: Engel J. Published year: 1998 Pages: 57-58/158 |
5.5 SubroutinesAnother kind of special branch is the jsr , for jump subroutine. It's like a goto that remembers where it came from. Or you can think of it as a method invocation that doesn't create a new stack frame. For example,
state1:
jsr get_next_character ; Go to the subroutine that leaves
; the next char on top of the stack
lookupswitch ; Jump to the next state
65: state1 ; Go to state 1 if the value is 65
66: state2 ; Go to state 2 if the value is 66
default: state3 ; Otherwise, go to state 3
get_next_character:
; At this point, there's a returnAddress on top of the stack
astore_3 ; Store it in variable 3
;; Do code to get the next character,
;; and leave it on top of the stack
ret 3 ; Return to the location in variable 3
The jsr is used to take code that would otherwise have to be replicated in several places and put it in a single place. It's not quite as powerful as putting it into a separate method, since there's no explicit facilities for passing arguments. It is less expensive to use, however, since the machine doesn't have to create a whole new stack frame. When jsr is executed, it branches to the location specified by the label, and it leaves a special kind of value on the stack called a returnAddress to represent the return address. It's your responsibility to remember this value by storing it in a local variable. The value is a kind of reference , so you use the aload and astore instructions to store its value. However, you can't call methods on it, store it in a field, or use it as an argument to a method. As with the other branches, the label must be inside the body of the same method as the jsr instruction. At the end of a subroutine, you can return to the instruction after the jsr with a ret . A ret is different from a return , which is used to return from an invocation. The argument to ret is a number that represents a local variable. That variable should contain the return address that was on the stack at the beginning of the subroutine. |
5.6 ExceptionsExceptions are sort of super- goto which can transfer control not only within a method, but even terminate the current method to find its destination further up the Java stack. Exceptions are used to indicate that something has gone wrong with a program. Usually, an exception indicates that the problem is fairly severe and the operation being attempted should be aborted. To indicate that a problem has occurred, the program throws an exception. When an exception is thrown, the JVM will try to find an exception handler that can handle the exception at the current location in the current method. This is called the active exception handler . If there is none, the JVM terminates the current method and checks again with the method that called the current method. It repeats this operation until it finds a handler. The handler should clean up the state of the system and correct the error, if possible. An exception is thrown with the athrow instruction. The athrow instruction takes one operand from the top of the stack: an object that must be a subclass of java/lang/Throwable . Usually, when you create an exception class, it is a subclass of either java/lang/Exception or java/lang/Error , depending on the nature of the error. This class declaration declares a SnackException , which indicates that some sort of problem has occurred while snacking: .class SnackException .super java/lang/Exception ; Exception is a Throwable ; A constructor which takes a message string .method <init> (Ljava/lang/String;)V aload_0 ; Initialize the superclass aload_1 invokespecial java/lang/Exception/<init> (Ljava/lang/String;)V return .end method A SnackException occurs when the program is having a snack and notices that it can't continue for some reason (for example, there's no more milk). To indicate that you're out of milk, use this code: ; I'm out of milk! new SnackException ; Create a SnackException dup ldc "Out of milk" ; Initialize it with a message invokespecial SnackException/<init> (Ljava/lang/String;)V athrow ; Throw the exception When an exception is thrown, control transfers to the most current exception handler . An exception handler is specified in Oolong with a .catch directive: .catch class from begin-label to end-label using handler-label Here, class is the type of exception to catch, and begin-label and end-label indicate the range of code within the method that is covered by the exception handler. This allows you to use different exception handlers for the same exception in different parts of the method. The handler-label designates which code will be called when the exception occurs. When an exception occurs and the program is currently between begin-label and end-label , control transfers to handler-label as long as the exception object is an instance of the class or one of its subclasses. If a SnackException is thrown, it can be caught by an exception handler catching SnackException s, Exception s, or Throwable s but not by an exception handler catching Error s, because SnackException is not a subclass of Error . Similarly, an exception handler catching SnackException s does not catch NullPointerException s, because NullPointerException is not a subclass of SnackException . For example,
.method nosh()V
.catch SnackException from begin to end using
SnackExceptionHandler
begin:
; Do stuff which might cause a SnackException
getstatic Refrigerator/milkQuantity I ; Do we have milk?
ifgt continue ; Drink if milk>0
; I'm out of milk!
new SnackException ; Create a SnackException
dup
ldc "Out of milk" ; Initialize it with a message
invokespecial SnackException/<init> (Ljava/lang/String;)V
athrow ; Throw the exception
continue:
; We do have milk, so we can drink some
end:
; The code here is not covered by the SnackException handler,
; because it is out of range.
return
SnackExceptionHandler:
;; Do something with the exception. Maybe notify the user
;; to go get some more milk.
return
.end method
An exception handler is active whenever the current instruction is between begin-label and end-label . "Between" means that the program counter is after begin-label and before end-label . When an athrow instruction is executed, the JVM looks to see if there is an exception handler active in the current method that can handle the exception. If there is one, then control transfers to the handler. The handler code begins with the thrown object on top of the stack. This is the object that was the operand to athrow . The following example shows what happens when you run out of cookies.
.catch SnackException from begin to end using handler
begin:
; Print a message
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "At begin"
invokevirtual java/io/PrintStream/println (Ljava/lang/String;)V
new SnackException ; Create a SnackException
dup
ldc "Out of cookies"
invokespecial SnackException/<init> (Ljava/lang/String;)V
athrow ; Throw it
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "This code doesn't get executed due to the exception"
invokevirtual java/io/PrintStream/println (Ljava/lang/String;)V
end:
return
handler:
; The exception is now on top of the stack
invokevirtual java/lang/Throwable/getMessage
()Ljava/lang/String;
getstatic java/lang/System/out Ljava/io/PrintStream;
swap ; Put out below the message
invokevirtual java/io/PrintStream/println (Ljava/lang/String;)V
return
When this code is executed, it prints At begin Out of cookies The code after the athrow is never executed, because control went to the handler instead. If there is no active exception handler that can handle the exception, then the current method is terminated and control passes back to the calling method right after the invocation. It's as if the method has returned, except that no value is returned, even if one was expected. The JVM continues looking for an exception handler from that point. This can continue indefinitely as long as no exception handler is found. If no exception handler is found anywhere , the exception is finally handled by the ThreadGroup , which usually prints out an error message like this one: java.lang.NullPointerException at Program.bottomOfTheStack(Program.java:18) at Program.middleOfTheStack(Program.java:14) at Program.topOfTheStack(Program.java:10) at Program.main(Program.java:5) Here's an example of how an exception can be thrown from one method invocation to another: .class BedtimeRoutine .method goToBed()V .catch SnackException from begin to end using handler ; This call is not covered by the exception handler invokestatic BedtimeRoutine/putOnPajamas()V ; SnackException coverage begins here begin: ; Invoke a function that might throw SnackException invokestatic BedtimeRoutine/haveMidnightSnack ()V ; SnackException coverage ends here end: ; This call is not covered by the exception handler either invokestatic BedtimeRoutine/brushTeeth()V return ;Successful completion handler: ;; Handle the exception that occured when trying to have ;; a snack return .end method .method static haveMidnightSnack()V getstatic Refrigerator/milkQuantity I ; Do we have milk? ifgt drink_milk ; Drink if got milk ; I'm out of milk! new SnackException ; Create a SnackException dup ldc "Out of milk" ; Initialize it with a message invokespecial SnackException/<init> (Ljava/lang/String)V athrow ; Throw the exception ; Successfully got milk. Now drink it. drink_milk: getstatic java/lang/System/out Ljava/io/PrintStream; ldc "Drinking milk" invokevirtual java/io/PrintStream/println (Ljava/lang/String;)V return .end method Suppose that program calls goToBed , which calls haveMidnightSnack . If there's milk, then everything is fine; you drink the milk, return to the goToBed method, and brush your teeth. If you discover that you're out of milk in haveMidnightSnack , a SnackException is thrown. There's no handler for it in haveMidnightSnack , so the method is terminated and control returns to goToBed . This method does have a handler for a SnackException . What happens there is not specified, but you might skip the midnight snack and go straight to bed, or you might run down to the store to buy more milk. 5.6.1 Multiple Exception HandlersWhen a particular instruction is covered by several exception handlers, the first applicable one within the frame is taken. For example,
.catch SnackException from begin2 to end2 using handler2
.catch java/lang/Exception from begin1 to end1 using handler1
begin1:
invokestatic BedtimeRoutine/putOnPajamas()V
begin2:
; Invoke a function that might throw SnackException
invokestatic BedtimeRoutine/haveMidnightSnack ()V
end2:
; This call is not covered by the exception handler either
invokestatic BedtimeRoutine/brushTeeth()V
end1:
return
handler2:
; The stack contains a SnackException; just return
return
handler1:
; The stack contains a more general Exception; print an
; error message
invokevirtual java/lang/Throwable/getMessage
()Ljava/lang/String;
getstatic java/lang/System/out Ljava/io/PrintStream;
swap ; Put out below the message
invokevirtual java/io/PrintStream/println
(Ljava/lang/String;)V
return
There is an exception handler that covers the entire range from begin1 to end1 . It handles the very general class java/lang/Exception , which covers exceptions like NullPointerException and ArrayIndexOutOfBoundsException , as well as SnackException s and most other application-defined Exception s. These are handled by the code at handler1 , which prints out the message associated with the exception. All exceptions inherit the method getMessage from java/lang/Throwable , which returns a detail message associated with the exception. The code from begin2 to end2 is covered by two different exception handlers. If a SnackException occurs, then it is handled by the code at handler2 . In this code, the snack exception isn't considered important, so the method just returns. If any other kind of Exception occurs, it is handled by the more general exception handler at handler1 . If a SnackException were to occur for some reason from brushTeeth or putOnPajamas , the handler at handler1 would handle the exception, since the other handler is not active while calling those two methods . Only the call to haveMidnightSnack is covered. It is important to realize that the order of the .catch directives matters. If a SnackException occurs during haveMidnightSnack , the handler at handler2 is used because its .catch directive comes first—not because SnackException is more specific than Exception . Suppose you reversed the .catch directives: .catch java/lang/Exception from begin1 to end1 using handler1 .catch SnackException from begin2 to end2 using handler2 In this case, the SnackException handler is totally hidden by the java/lang/Exception handler. The code at handler2 will never be called, because any SnackException thrown will be handled by the first exception handler. As a general rule, write your code so that the more specific exception handlers come before the more general ones.
5.6.2 .throws DirectiveYou can alert somebody using your method to what exceptions the method might throw with the .throws directive: .method lateNightSnack()V .throws OutofMilkException .throws OutofCookiesException The .throws directives are optional. They're used to support a Java language requirement that a method can throw only exceptions that are explicitly listed. The following Java method—
void eveningActivities()
{
watchTV();
lateNightSnack();
goToBed();
}
—will evoke an error from the Java compiler stating that the OutOfCookiesException and OutOfMilkException exceptions must either be caught or listed in the throws clause of the method declaration: void eveningActivities() throws OutOfMilk, OutOfCookies If you don't list the possible exceptions in a .throws clause, then the Java compiler won't know that your method might throw an exception, and somebody using the eveningActivities method would be surprised if the method terminates abnormally. Writing .throws directives is more work for you, but it makes life easier on the next programmer who uses the code. 5.6.3 Other Ways to Throw ExceptionsBesides athrow , exceptions can also be thrown by other untoward events happening in the virtual machine. For example, this code tries to divide 1 by 0: iconst_1 iconst_0 idiv It results in an ArithmeticException when you execute the program. Similarly, attempting to call a method on null will cause an exception: aload_null invokevirtual java/lang/Object/toString ()Ljava/lang/String; This will cause a NullPointerException . These machine-signaled exceptions can be caught with a .catch directive, just like any other exception.
|
|
Programming for the Java Virtual Machine Authors: Engel J. Published year: 1998 Pages: 57-58/158 |
![]() Inside the Java 2 Virtual Machine | ![]() Java Performance | ![]() Javau2122 Virtual Machine Specification, The (2nd Edition) | ![]() Effective Java (2nd Edition) | ![]() Java Virtual Machine (Java Series) |
![]() Inside the Java 2 Virtual Machine | ![]() Java Performance |
![]() Javau2122 Virtual Machine Specification, The (2nd Edition) | ![]() Effective Java (2nd Edition) |
![]() Java Virtual Machine (Java Series) |