Bytecode basics

A first look at the bytecodes of the Java virtual machine

1 2 Page 2
Page 2 of 2
OpcodeOperand(s)Description
lloadvindexpushes long from local variable positions vindex and (vindex + 1)
lload_0(none)pushes long from local variable positions zero and one
lload_1(none)pushes long from local variable positions one and two
lload_2(none)pushes long from local variable positions two and three
lload_3(none)pushes long from local variable positions three and four
dloadvindexpushes double from local variable positions vindex and (vindex + 1)
dload_0(none)pushes double from local variable positions zero and one
dload_1(none)pushes double from local variable positions one and two
dload_2(none)pushes double from local variable positions two and three
dload_3(none)pushes double from local variable positions three and four

The final group of opcodes that push local variables move 32-bit object references from the local variables section of the stack frame to the operand section. These opcodes are shown in the following table:

OpcodeOperand(s)Description
aloadvindexpushes object reference from local variable position vindex
aload_0(none)pushes object reference from local variable position zero
aload_1(none)pushes object reference from local variable position one
aload_2(none)pushes object reference from local variable position two
aload_3(none)pushes object reference from local variable position three

Popping to local variables

For each opcode that pushes a local variable onto the stack there exists a corresponding opcode that pops the top of the stack back into the local variable. The names of these opcodes can be formed by replacing "load" in the names of the push opcodes with "store". The opcodes that pop ints and floats from the top of the operand stack to a local variable are listed in the following table. Each of these opcodes moves one 32-bit value from the top of the stack to a local variable.

OpcodeOperand(s)Description
istorevindexpops int to local variable position vindex
istore_0(none)pops int to local variable position zero
istore_1(none)pops int to local variable position one
istore_2(none)pops int to local variable position two
istore_3(none)pops int to local variable position three
fstorevindexpops float to local variable position vindex
fstore_0(none)pops float to local variable position zero
fstore_1(none)pops float to local variable position one
fstore_2(none)pops float to local variable position two
fstore_3(none)pops float to local variable position three

The next table shows the instructions that pop values of type long and double into a local variable. These instructions move a 64-bit value from the top of the operand stack to a local variable.

OpcodeOperand(s)Description
lstorevindexpops long to local variable positions vindex and (vindex + 1)
lstore_0(none)pops long to local variable positions zero and one
lstore_1(none)pops long to local variable positions one and two
lstore_2(none)pops long to local variable positions two and three
lstore_3(none)pops long to local variable positions three and four
dstorevindexpops double to local variable positions vindex and (vindex + 1)
dstore_0(none)pops double to local variable positions zero and one
dstore_1(none)pops double to local variable positions one and two
dstore_2(none)pops double to local variable positions two and three
dstore_3(none)pops double to local variable positions three and four

The final group of opcodes that pops to local variables are shown in the following table. These opcodes pop a 32-bit object reference from the top of the operand stack to a local variable.

OpcodeOperand(s)Description
astorevindexpops object reference to local variable position vindex
astore_0(none)pops object reference to local variable position zero
astore_1(none)pops object reference to local variable position one
astore_2(none)pops object reference to local variable position two
astore_3(none)pops object reference to local variable position three

Type conversions

The Java virtual machine has many opcodes that convert from one primitive type to another. No operands follow the conversion opcodes in the bytecode stream. The value to convert is taken from the top of the stack. The JVM pops the value at the top of the stack, converts it, and pushes the result back onto the stack. Opcodes that convert between int, long, float, and double are shown in the following table. There is an opcode for each possible from-to combination of these four types:

OpcodeOperand(s)Description
i2l(none)converts int to long
i2f(none)converts int to float
i2d(none)converts int to double
l2i(none)converts long to int
l2f(none)converts long to float
l2d(none)converts long to double
f2i(none)converts float to int
f2l(none)converts float to long
f2d(none)converts float to double
d2i(none)converts double to int
d2l(none)converts double to long
d2f(none)converts double to float

Opcodes that convert from an int to a type smaller than int are shown in the following table. No opcodes exist that convert directly from a long, float, or double to the types smaller than int. Therefore converting from a float to a byte, for example, would require two steps. First the float must be converted to an int with f2i, then the resulting int can be converted to a byte with int2byte.

OpcodeOperand(s)Description
int2byte(none)converts int to byte
int2char(none)converts int to char
int2short(none)converts int to short

Although opcodes exist that convert an int to primitive types smaller than int (byte, short, and char), no opcodes exist that convert in the opposite direction. This is because any bytes, shorts, or chars are effectively converted to int before being pushed onto the stack. Arithmetic operations upon bytes, shorts, and chars are done by first converting the values to int, performing the arithmetic operations on the ints, and being happy with an int result. This means that if you add 2 bytes you get an int, and if you want a byte result you must explicitly convert the int result back to a byte. For example, the following code won't compile:

 class BadArithmetic {
        byte addOneAndOne() {
                byte a = 1;
                byte b = 1;
                byte c = a + b;
                return c;
        }
}

When presented with the above code, javac objects with the following remark:

 BadArithmetic.java(7): Incompatible type for declaration. Explicit cast needed to convert int to byte.
                byte c = a + b;
                     ^

To remedy the situation, the Java programmer must explicitly convert the int result of the addition of a + b back to a byte, as in the following code:

 class GoodArithmetic {
        byte addOneAndOne() {
                byte a = 1;
                byte b = 1;
                byte c = (byte) (a + b);
                return c;
        }
}

This makes javac so happy it drops a GoodArithmetic.class file, which contains the following bytecode sequence for the addOneAndOne() method:

 iconst_1          // Push int constant 1.
istore_1          // Pop into local variable 1, which is a: byte a = 1;
iconst_1          // Push int constant 1 again.
istore_2          // Pop into local variable 2, which is b: byte b = 1;
iload_1           // Push a (a is already stored as an int in local variable 1).
iload_2           // Push b (b is already stored as an int in local variable 2).
iadd              // Perform addition. Top of stack is now (a + b), an int.
int2byte          // Convert int result to byte (result still occupies 32 bits).
istore_3          // Pop into local variable 3, which is byte c: byte c = (byte) (a + b);
iload_3           // Push the value of c so it can be returned.
ireturn           // Proudly return the result of the addition: return c;

Conversion diversion: a JVM simulation

The applet below demonstrates a JVM executing a sequence of bytecodes. The bytecode sequence in the simulation was generated by

javac

for the Convert() method of the class shown below:

 class Diversion {
    static void Convert() {
        byte imByte = 0;
        int imInt = 125;
        while (true) {
            ++imInt;
            imByte = (byte) imInt;
            imInt *= -1;
            imByte = (byte) imInt;
            imInt *= -1;
        }
    }
}

The actual bytecodes generated by javac for Convert() are shown below:

 iconst_0          // Push int constant 0.
istore_0          // Pop to local variable 0, which is imByte: byte imByte = 0;
bipush 125        // Expand byte constant 125 to int and push.
istore_1          // Pop to local variable 1, which is imInt: int imInt = 125;
iinc 1 1          // Increment local variable 1 (imInt) by 1: ++imInt;
iload_1           // Push local variable 1 (imInt).
int2byte          // Truncate and sign extend top of stack so it has valid byte value.
istore_0          // Pop to local variable 0 (imByte): imByte = (byte) imInt;
iload_1           // Push local variable 1 (imInt) again.
iconst_m1         // Push integer -1.
imul              // Pop top two ints, multiply, push result.
istore_1          // Pop result of multiply to local variable 1 (imInt): imInt *= -1;
iload_1           // Push local variable 1 (imInt).
int2byte          // Truncate and sign extend top of stack so it has valid byte value.
istore_0          // Pop to local variable 0 (imByte): imByte = (byte) imInt;
iload_1           // Push local variable 1 (imInt) again.
iconst_m1         // Push integer -1.
imul              // Pop top two ints, multiply, push result.
istore_1          // Pop result of multiply to local variable 1 (imInt): imInt *= -1;
goto 5            // Jump back to the iinc instruction: while (true) {} 

The Convert() method demonstrates the manner in which the JVM converts from int to byte. imInt starts out as 125. Each pass through the while loop, it is incremented and converted to a byte. Then it is multiplied by -1 and again converted to a byte. The simulation quickly shows what happens at the edges of the valid range for the byte type.

The maximum value for a byte is 127. The minimum value is -128. Values of type int that are within this range convert directly to byte. However, as soon as the int gets beyond the valid range for byte, things get interesting.

The JVM converts an int to a byte by truncating and sign extending. The highest order bit, the "sign bit," of longs, ints, shorts, and bytes indicate whether or not the integer value is positive or negative. If the sign bit is zero, the value is positive. If the sign bit is one, the value is negative. Bit 7 of a byte value is its sign bit. To convert an int to a byte, bit 7 of the int is copied to bits 8 through 31. This produces an int that has the same numerical value that the int's lowest order byte would have if it were interpreted as a byte type. After the truncation and sign extension, the int will contain a valid byte value.

The simulation applet shows what happens when an int that is just beyond the valid range for byte types gets converted to a byte. For example, when the imInt variable has a value of 128 (0x00000080) and is converted to byte, the resulting byte value is -128 (0xffffff80). Later, when the imInt variable has a value of -129 (0xffffff7f) and is converted to byte, the resulting byte value is 127 (0x0000007f).

To drive the simulation, just press the "Step" button. Each press of the "Step" button will cause the JVM to execute one bytecode instruction. To start the simulation over, press the "Reset" button. There is a text area at the bottom of the applet that describes the next instruction to be executed. Happy clicking.

Click here for the source code of Conversion Diversion.

The small print: "Bytecode Basics" Article Copyright (c) 1996 Bill Venners. All rights reserved. "Conversion Diversion" Applet Copyright (c) 1996 Artima Software Company. All rights reserved.

Bill Venners provides custom software development and consulting services in Silicon Valley under under the name Artima Software Company. He has been object oriented for five years, primarily working in C++ on MS Windows. Before that he did a lot of C on Unix and assembly language on various microprocessors. He is currently focused on Java.

Learn more about this topic

  • 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 JVM.
  • 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 JVM in particular.

1 2 Page 2
Page 2 of 2