Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Control flow

With code samples, tables, and a Java virtual machine simulation, here's a look at the bytecodes of the Java virtual machine that deal with control flow

  • Print
  • Feedback

Page 2 of 2

Table jumping


Opcode Operand(s) Description


lookupswitch <0-3 byte pad>defaultbyte1, defaultbyte2, defaultbyte3, defaultbyte4, npairs1, npairs2, npairs3, npairs4, case value/branch offset pairs... pop key, match key with case values, if match found jump to associated branch offset, else jump to default branch offset


tableswitch <0-3 byte pad>defaultbyte1, defaultbyte2, defaultbyte3, defaultbyte4, lowbyte1, lowbyte2, lowbyte3, lowbyte4, highbyte1, highbyte2, highbyte3, highbyte4, branch offsets... pop key, if not in low/high range jump to default branch offset, else get the (key - low) branch offset and jump


Other than the opcodes described above, the only Java virtual machine instructions that affect control flow are those that deal with throwing and catching exceptions, try-finally clauses, and invoking and returning from methods. The bytecodes for exceptions and try-finally clauses were discussed in the previous two installments of this column (see Resources). The bytecodes that deal with invoking and returning from methods will be treated in a future installment.

SayingTomato: a Java virtual machine simulation

The applet below demonstrates a Java virtual machine executing a sequence of bytecodes. The bytecode sequence in the simulation was generated by the javac compiler for the argue() method of the class shown below:

class Argument {

public final static int TOMAYTO = 0; public final static int TOMAHTO = 1;
static void argue() {
int say = TOMAYTO;
while (true) {
switch (say) {
case TOMAYTO:
say = TOMAHTO; break;
case TOMAHTO:
say = TOMAYTO; break; } } } }


The bytecodes generated by javac for the argue() method are shown below:

 
   0 iconst_0             // Push constant 0 (TOMAYTO)
   1 istore_0             // Pop into local var 0: int say = TOMAYTO;
   2 iload_0              // Push key for switch from local var 0
                          // Perform switch statement: switch (say) {...
                          // Low case value is 0, high case value is 1
                          // Default branch offset will goto 2
   3 tableswitch 0 to 1: default=2
            0: 24         // case 0 (TOMAYTO): goto 24
            1: 29         // case 1 (TOMAHTO): goto 29

// Note that the next instruction starts at address 24, // which means that the tableswitch took up 21 bytes 24 iconst_1 // Push constant 1 (TOMAHTO) 25 istore_0 // Pop into local var 0: say = TOMAHTO 26 goto 2 // Branch unconditionally to 2, top of while loop 29 iconst_0 // Push constant 1 (TOMAYTO) 30 istore_0 // Pop into local var 0: say = TOMAYTO 31 goto 2 // Branch unconditionally to 2, top of while loop


The argue() method merely switches the value of say back and forth between TOMAYTO and TOMAHTO. Because the values of TOMAYTO and TOMAHTO were consecutive (TOMAYTO was a 0 and TOMAHTO was a 1), the javac compiler used a tableswitch. The tableswitch is a more efficient instruction than a lookupswitch, and the equivalent lookupswitch instruction would occupy 28 bytes -- 4 bytes more than the tableswitch instruction.

It turns out that even if TOMAYTO were a 0 and TOMAHTO were a 2, the javac compiler still would have used a tableswitch, because even with the extra default branch offset in there for a 1, the tableswitch instruction would occupy only 28 bytes -- the same number of bytes as the equivalent lookupswitch. Both instructions occupy the same number of bytes, but tableswitch is more efficient, so it is used. As soon as you make TOMAHTO a 3, however, javac starts using a lookupswitch. This is because a tableswitch now would need two default branch offsets in its list (for 1 and 2), which would push its size up to 32 bytes. Thus, a lookupswitch now would require fewer bytes than a tableswitch -- so javac would choose the lookupswitch.

The branch offsets for the case values cause the Java virtual machine to hop down to code that will change the value of the say local variable. The value of say will alternate between TOMAYTO and TOMAHTO indefinitely, until the user aborts the program, thereby calling the whole thing off.

Get in the driver's seat

To drive the simulation, just press the Step button. Each press of the Step button will cause the Java virtual machine to execute one bytecode instruction. To start the simulation over, press the Reset button. To cause the JVM to repeatedly execute bytecodes with no further coaxing on your part, press the Run button. The JVM will then execute the bytecodes until the Stop button is pressed. The text area at the bottom of the applet describes the next instruction to be executed. Happy clicking.

About the author

Bill Venners has been writing software professionally for 12 years. Based in Silicon Valley, he provides software consulting and training services under the name Artima Software Company. Over the years he has developed software for the consumer electronics, education, semiconductor, and life insurance industries. He has programmed in many languages on many platforms: assembly language on various microprocessors, C on Unix, C++ on Windows, Java on the Web. He is author of the book: Inside the Java Virtual Machine, published by McGraw-Hill.
  • Print
  • Feedback

Resources
  • Previous Under The Hood articles:
  • The lean, mean virtual machine -- Gives an introduction to the Java virtual machine. Look here to see how the garbage collected heap fits in with the other parts of the Java virtual machine.
  • The Java class file lifestyle -- Gives an overview to the Java class file, the file format into which all Java programs are compiled.
  • Java's garbage-collected heap -- Gives an overview of garbage collection in general and the garbage-collected heap of the Java virtual machine in particular.
  • Bytecode basics -- Introduces the bytecodes of the Java virtual machine, and discusses primitive types, conversion operations, and stack operations in particular.
  • Floating Point Arithmetic -- Describes the Java virtual machine's floating-point support and the bytecodes that perform floating point operations.
  • Logic and Arithmetic -- Describes the Java virtual machine's support for logical and integer arithmetic, and the related bytecodes.
  • Objects and Arrays -- Describes how the Java virtual machine deals with objects and arrays, and discusses the relevant bytecodes.
  • Exceptions -- Describes how the Java virtual machine deals with exceptions, and discusses the relevant bytecodes.
  • Try-Finally -- Describes how the Java virtual machine implements try-finally clauses, and discusses the relevant bytecodes.