- Special Edition Using Java, 2nd Edition -

Chapter 36

Inside the Java Virtual Machine


by Jordan Olin

The concept and implementation of virtual machines (VM) have been around for quite some time. One of the earliest commercial environments with this architecture was the UCSD p-System. This system was created by Dr. Kenneth Bowles at the University of Southern California, San Diego, in the 1970s. Dr. Bowles was able to spin off a company to market the operating system called SofTek Microsystems. In fact, the p-System was the core operating system for the Apple ///. It was also the alternative operating system to PC-DOS for the IBM PC after the PC’s introduction in 1980.

Like the Java environment, the UCSD p-System was based on a primary language (Pascal). The UCSD p-System had a set of primitive core libraries, a machine-independent object file format, a set of byte-oriented pseudocodes, and a virtual machine definition to interpret them. The p-System and its version of Pascal even had advanced features such as a full-screen user interface, concurrency primitives, and a dynamic library mechanism called units. The p-System was ported to many architectures and had wide-spread success in the vertical software market.

So, if the p-System was so much like Java, why isn’t it still around today? When I asked Sun’s chief technology officer, Eric Schmidt, this question, he simply replied, “Have you ever known a university that knew how to market software?” The point of this is that Java is not so unique or new. The Java environment is a success because it has the sponsorship of a very successful company and a much more mature industry.

Elements of the JVM

When you look at the Java environment, you see five major elements:

Of these items, the last three are really what enabled Java technology to become as widespread (or ubiquitous) as quickly as it has. Thus, the designers of Java gained almost instant portability of any .class file to any computer/chip-set with an implementation of the JVM. This portability applied regardless of what kind of host computer/chip-set was used to compile the source. The concept of “write once, run anywhere” is being realized because of the widespread implementation of the JVM on a wide array of hardware platforms and architectures.

The remainder of this section describes some of the technical details involved with Sun’s implementation of the JVM. Clearly, many vendors have created JVM implementations (Natural Intelligence, Netscape, Microsoft, and more). All of the vendors have contributed some unique features to their implementations. But, what is fundamentally important is that they all support Sun’s initial specification for the “.class” file structure, bytecode definitions, and virtual machine.

The Architecture of a Virtual Machine

So, what really is a virtual machine? It is a software concept that is based on the notion of an imaginary computer with a logical set of instructions, or pseudocodes, that define the operations this computer can perform. A VM-oriented compiler will typically take some source language. Instead of generating machine code instructions targeted to a particular hardware architecture, it generates pseudocode streams that are based on the imaginary computer’s instruction set.

The other side of the equation is how these instructions get executed. This is where an interpreter, or what the Java world has been referring to as the virtual machine, takes its role. An interpreter is really just an application that understands the semantics of the pseudocodes for this imaginary computer and converts them to machine code instructions for the underlying hardware to which the interpreter has been targeted. The VM also creates a runtime system internally to support implementing the semantics of the instructions as they are executed. The runtime system is also responsible for loading object (or .class) files, memory management, and garbage collection.

Because of the inconsistency in the hardware platform facilities that are used to host a VM, they are typically based on the concept of a stack machine. A stack machine does not use any physical registers to pass information between instructions. Instead, it uses a stack to hold frames representing the state of a method, operands to the bytecodes, space for arguments to the methods, and space for local variables. There is one pseudoregister called the program counter—a pointer into the bytecode array of the currently executing instruction.

The actual logic for the interpreter phase of the VM is a very simple loop. The following diagram represents a flowchart view of the logic that is typically used by a stack-based VM interpreter.


FIG. 36.1

The JVM interpreter loop.

There are two important points to note about how the interpreter actually processes the bytecode instructions:

For example, there are bytecodes that push values from the Constant Pool onto the stack. These bytecodes have, as an argument, the index of the value in the Constant Pool. When the semantic for that bytecode is complete, the value will be on the top of the stack, and the Program Counter will point to the bytecode immediately following the argument. Here is what the bytecode stream might look like for the Load Constant-2 Bytecode:

ldc2 index byte 1 index byte 2 <next bytecode>

Something else that may not be readily apparent is that method calls, exception handlers, and monitors (the locks used by the synchronize language keyword) are all handled by specific bytecodes. They are not the responsibility of the interpreter loop itself. The loop is very stupid in that all it knows how to do is to get a bytecode and fire off its associated semantic routine.

Finally, there are other techniques that an interpreter may use to process the stream of bytecodes representing the executable instructions for the VM. One common optimized interpreter technique is called a threaded interpreter (not to be confused with multithreading). A threaded interpreter does not use a loop-based approach to traverse the stream of bytecodes. Instead, the interpreter actually jumps from bytecode semantic to bytecode semantic in a similar way that a needle and thread are used to make stitches. The big advantage to this technique is that there is no overhead for the interpreter loop, the instructions are executed, and a simple jump is performed at the end. As this is an implementation choice, it does not affect the .class file structure and, hence, is an option open to people developing their own VMs.

The other optimization that is becoming increasingly popular is the use of what is called a Just-In-Time compiler, or JIT compiler. The Microsoft JVM, Microsoft Internet Explorer 3.0, and Netscape Navigator 3.0 all include JIT technology. The idea of the JIT compiler is that instead of interpreting each instruction of the bytecode stream, the set of bytecodes is directly translated into an equivalent set of machine code instructions for the target system at runtime. This new translated machine code version of the method is then stored and used whenever a call is made to that particular method. So, you get the portability based on the .class file and bytecodes. You also get close-to-native code performance after taking the one-time, up-front translation from bytecode to machine code.

Now that you have a better feel for the architecture of the VM, let’s examine how it deals with memory management.

Memory Management and Garbage Collection

One of the major decisions that implementers of runtime systems face is how to handle dynamic memory requirements that are placed on the systems by the programs that are executing within them. The runtime system designer must choose between making the user of the system responsible for memory management or making the runtime system smart enough to handle this task.

If you have ever coded in any compiled 3GL such as C, C++, or Pascal, then you have experienced the “pleasure of handling your own memory management. These languages have runtime systems that give you primitive methods for allocating and deallocating arbitrarily sized blocks of memory from a larger chunk of memory called a heap. Allocating your application’s memory needs from the heap is not a problem if your application has relatively few dynamic memory requirements, but most object-oriented applications tend to create and destroy relatively small objects on a frequent basis.

In order to help deal with this problem, most runtime systems have a heap manager that actively maintains the heap in a state where memory is available as much as possible. One of the major problems that the heap manager tries to solve is called fragmentation, which is a result of allocating and deallocating lots of small, nonuniform pieces of memory from the heap. Typically, a heap is managed by tracking memory in two lists:

When a request is made to the heap manager for a chunk of memory, the free list is searched for a block that can fulfill this request. Most modern heap managers keep the free list in ascending order of the free blocks’ sizes. This allows the allocation mechanism to use a “first-fit strategy,” finding the first-available, smallest block of memory that can satisfy the request. This strategy helps keep fragmentation of the heap to a minimum.

Another technique that heap managers use to keep fragmentation of the heap to a minimum is called coalescing. As memory is returned to the heap, a new block is placed in the free list. As this process occurs, the free list is examined to see if the piece of memory being returned immediately precedes or follows another free block. If this is the case, then the two blocks are merged together, creating one larger free block.

Another issue in heap management is how to deal with a request for more memory than an individual block on the free list can provide. Such a request requires that the heap manager take some very proactive steps in order to create more memory. The solution is a technique called compaction. Compaction is the process of merging all free blocks together by moving the allocated memory (memory between the free blocks) to one end of the heap, thereby creating one large, coalesced free block. The real difficulty with this process is that the runtime system must know the location of every variable (stack-based or dynamic) that refers to any heap-based object in memory. The system must then update the variable with the new location of the object that it refers to. This process is very expensive in both time and memory overhead, but compaction is a reality in any heap-based allocation system.

The other major problem with being responsible for your own memory allocation and, specifically, deallocation is the concept of dangling references, or garbage. This refers to objects in memory that you allocated but have lost the reference (or pointer) to, so you cannot explicitly de-allocate the memory. Dangling references are very easy to create. The following C++ code shows a typical way to create a dangling reference:

int *iArray;
// Create initial array
iArray = new int[3];

// Grow the array
if (iArrayCount == 3) {
int *tempArray = new int[6];
for (int i = 0;i < 3;++i) tempArray[I] = iArray[I];
// MAKE A DANGLING REFERENCE:
iArray = tempArray;
}

Once iArray is overwritten with tempArray, the memory chunk originally pointed to by iArray is orphaned and now garbage. The memory chunk cannot be reclaimed during compaction, as it is not on the free list. The only way to deal with this situation is via garbage collection.

Garbage collection is a technique in which all allocated memory objects that are no longer needed or referred to may be reclaimed back to the free list without an explicit de-allocation. It is a process of the heap management system, and memory blocks must be structured in specific ways to take advantage of it. Two common garbage collection techniques can be used: reference counting, and mark and sweep.

Reference counting requires that each object instance in the heap maintain a field called the reference count. As a field or variable is assigned a reference to an object, that object’s reference count is increased by one. When the field or variable that refers to the object goes out of scope or is destroyed, the reference count is decreased by one. When an object’s reference count reaches zero, it is no longer in use and its space may be collected. This algorithm is fast during collection but has a performance penalty when any assignment is without objects or an object is passed as an argument. For each of these situations, the reference count must be maintained at runtime, causing general slow-downs of the runtime system.

The mark and sweep algorithm requires that each object contain a bit field called the mark bit, or it is required that an external array is created when the algorithm runs to hold the mark bit. The algorithm begins by traversing all allocated blocks of memory in the heap and resetting the marked bit for that block. Next, examine all fields and variables that refer to objects in the heap, setting the marked bit of the heap object to true. Finally, sweep through the allocated heap objects and look for any that are not marked. Then, either reclaim the space by putting the unused objects on the free list, or copy the “live” objects to the end of the heap. Then, reclaim the original area back to the free list and compact (a variant of mark and sweep known as stop and copy). This algorithm has low storage overhead and does not affect runtime performance overall but may cause longer-than-desired lags in time when the garbage collector runs.

Now that you can see how difficult a problem heap and memory management can be, especially for the non-Java developer, let’s take a look at how the Java runtime system handles these problems.

First, the JVM uses two separate heaps for dynamic and static memory allocation. All class definitions, the Constant Pool, and method tables are kept in a nongarbage collected heap. So, once a class definition has been read in, the structural information and methods stay in memory. This does add a little storage overhead but improves performance for classes that come and go relatively frequently within an application.

The second heap is split into two areas that grow in opposite directions. One area is used to hold object instances, and the other contains “handles” to those instances. The runtime image of fields and variables in your Java application that reference object instances do not actually contain pointers to those objects. They contain pointers to a special, fixed-size, heap-based memory object called a handle. The handle is a structure that contains two pointers, one to the object’s method table and the other to the actual object instance. The advantage to this layout is that the handles never move in memory, so there is never a need to keep track of which variables point to which objects when updating pointers after compacting. You simply update the pointer value of the handle structure.

The object space of the heap is managed in a traditional fashion in that there is a free list and an allocated object list. As objects are instantiated, the free list is searched for the “first-fit block.” Also, if possible, coalescing happens during this phase (as opposed to when an instance is put back on the free list) in order to make the garbage collection process faster. In addition, the dangling reference problem is eliminated by Java, as you are not responsible for explicit object deallocations. The Java language has a new operator but no corresponding delete.

The garbage collection algorithm used by the Java VM applies to all objects in the dynamic heap. The algorithm runs synchronously whenever the heap manager cannot find any memory in the free space list. Or, it may also run asynchronously in that a thread for the garbage collector is kicked off whenever the system is idle for a sufficient period of time. (This is of dubious value, as the asynchronous garbage collector will be interrupted and have to start again if a runnable class becomes ready.) And, you may manually initiate the garbage collection algorithm by calling the method System.gc(). For highly interactive applications where idle processing may be at a minimum, you might occasionally want to call the garbage collector manually.

The actual garbage collector used by the JVM is an implementation of the stop-and-copy algorithm. But, there is a difference. Normally, after the garbage collector finishes its compaction phase, all variables and fields that relate to an object would need to change. But, because all object reference variables are handle based, we don’t need to find and update all variables that point to active objects. You can simply update the handle in the heap to point to the just-moved object instance. The algorithm is pretty fast but not ready for real-time applications.

One last aspect to the JVM’s garbage collector is the notion of a Finalizer method. A Finalizer is a special method called finalize that is declared in the base class java.lang.Object. It has the following prototype:

protected void finalize () throws Throwable;

The finalize method is for cleaning up external resources (such as open files) that would not normally be performed in routine garbage collection. The garbage collector calls the finalize method just prior to garbage collecting an object instance. The problem is that garbage collection is not run immediately when you call the System.gc() method; it is simply scheduled to run. The garbage collector thread runs at a very low priority and may get interrupted frequently. In fact, the garbage collector may never get to dispose of your object before the application terminates. So, generally speaking, the usefulness of implementing the finalize method is questionable.

You can do one other trick in a finalize method—”resurrect” an object instance. It is possible for you to place the value of the this field into some other object reference and stop the object from being garbage collected. At that point, though, the garbage collector will not call the finalize method again, even when the object instance is really ready for garbage collection.

That’s all there is to heap management in Java: one heap contains a fixed table of class information and methods, and another heap holds the handle table and object instances. You don’t explicitly deallocate anything (although setting an unused object variable to null will act as a hint to the garbage collector and heap), and the garbage collector may be run manually by calling System.gc().

Class File Verification

The last real algorithm that I cover for the JVM is verification. Verification is a process that is applied to certain class files as they are loaded. Because Java is oriented toward applications whose pieces (.class files) are potentially scattered anywhere around the globe, you need a mechanism that can prove these nonlocal classes can be properly executed by the JVM. By default, the java command will put all classes that were not loaded from the local hard drive through the verification process. Whether a class is put through the verification process is a function of the Class Loader and is controlled by arguments that you may specify on the java command line. In the case of browsers that support Java applets, all nonsystem classes are put through verification.

The verifier exists basically to subvert or thwart any attempts to create or pass off a hostile .class file. Because classes are loaded over the network from a typically unknown source, the verifier is applied to them to make sure they conform to the contract between a .class file and the JVM specification. Another benefit of the verification step is that it speeds execution of the Java bytecodes at runtime. The speed is increased because the form is good, and the bytecodes don’t have to verify their own arguments at each execution. The verifier also checks the overall integrity of the .class file.

The verification process can be broken up into four phases. The first three are performed at class-load time, with the fourth performed by a subset of the actual Java bytecodes.

The first phase could be called the syntax check phase. It is responsible for ensuring the structural and syntactic integrity of the .class file being loaded. The following areas are examined during this phase:

The second phase is used to check the “semantic” consistency of the .class file. It is responsible for checking the following areas:

The third phase is the most intense and is called the bytecode verifier. This phase performs a data-flow analysis on the actual bytecode stream that is contained in each method definition in the class. The following list represents the major features of the bytecode verifier, which ensures the following:

The final phase is actually performed at runtime and involves checks that could not be performed in phase three, as not all referenced classes are necessarily loaded in that phase. For each instruction that dynamically refers to another class (either a field or method), the linkage is examined. Then, the access permissions are checked. Furthermore, if all is OK, the referenced class is instantiated. Also, if the current bytecode references anything in the Constant Pool, it is resolved, and a special _quick variant of the bytecode instruction is replaced at that point in the bytecode stream. The _quick variants assume that the value required is directly accessible with no intermediate Constant Pool resolution requirement.

The JVM Bytecodes

This final section is a reference for the actual JVM bytecode instructions. There is not enough space in this section to include all of the details that are actually required to implement a JVM, but you should be able to write a simple bytecode disassembler with this information. The reference is in tabular format with the following columns:

When the JVM interpreter loop is running, there is actually one logical register that is used—the Program Counter, which represents the address in the bytecode stream of the currently executing instruction. Some of the instructions modify this Program Counter in order to alter the flow of execution. Otherwise, execution flows sequentially through the bytecode stream from instruction to instruction.

Table 36.1 Java Bytecode Instructions in OpCode Order

Instruction OpCode #Args Description
nop 0 0 Does nothing, a No OPeration.
aconst_null 1 0 Pushes the null object reference on the stack.
iconst_m1 2 0 Pushes the integer constant -1 on the stack.
iconst_0 3 0 Pushes the integer constant 0 on the stack.
iconst_1 4 0 Pushes the integer constant 1 on the stack.
iconst_2 5 0 Pushes the integer constant 2 on the stack.
iconst_3 6 0 Pushes the integer constant 3 on the stack.
iconst_4 7 0 Pushes the integer constant 4 on the stack.
iconst_5 8 0 Pushes the integer constant 5 on the stack.
lconst_0 9 0 Pushes the long constant 0 on the stack.
lconst_1 10 0 Pushes the long constant 1 on the stack.
fconst_0 11 0 Pushes the float constant 0 on the stack.
fconst_1 12 0 Pushes the float constant 1 on the stack.
fconst_2 13 0 Pushes the float constant 2 on the stack.
dconst_0 14 0 Pushes the double constant 0 on the stack.
dconst_1 15 0 Pushes the double constant 1 on the stack.
bipush 16 1 Pushes a 1-byte signed value on the stack as an integer.
sipush 17 2 Pushes a 16-bit signed value on the stack as an integer.
ldc1 18 1 Uses arg as an 8-bit index into the constant pool and puts the associated item on the stack.
ldc2 19 2 Uses arg as a 16-bit index into the constant pool and puts the associated item on the stack.
ldc2w 20 2 Uses arg as a 16-bit index into the constant pool and pushes the long or double at that position on the stack.
iload 21 1 Pushes the value of the integer local variable at the index specified by the argument in the current method frame on the stack.
lload 22 1 Pushes the value of the long local variable at the index and index+1 specified by the argument in the current method frame on the stack.
fload 23 1 Pushes the value of the float local variable at the index specified by the argument in the current method frame on the stack.
dload 24 1 Pushes the value of the double local variable at the index and index+1 specified by the argument in the current method frame on the stack.
aload 25 1 Pushes the value of the object reference local variable at the index specified by the argument in the current method frame on the stack.
iload_0 26 0 Pushes the value of the integer local variable at index 0 in the current method frame on the stack.
iload_1 27 0 Pushes the value of the integer local variable at index 1 in the current method frame on the stack.
iload_2 28 0 Pushes the value of the integer local variable at index 2 in the current method frame on the stack.
iload_3 29 0 Pushes the value of the integer local variable at index 3 in the current method frame on the stack.
lload_0 30 0 Pushes the value of the long local variable at index 0 and 1 in the current method frame on the stack.
lload_1 31 0 Pushes the value of the long local variable at index 1 and 2 in the current method frame on the stack.
lload_2 32 0 Pushes the value of the long local variable at index 2 and 3 in the current method frame on the stack.
lload_3 33 0 Pushes the value of the long local variable at index 3 and 4 in the current method frame on the stack.
fload_0 34 0 Pushes the value of the float local variable at index 0 in the current method frame on the stack.
fload_1 35 0 Pushes the value of the float local variable at index 1 in the current method frame on the stack.
fload_2 36 0 Pushes the value of the float local variable at index 2 in the current method frame on the stack.
fload_3 37 0 Pushes the value of the float local variable at index 3 in the current method frame on the stack.
dload_0 38 0 Pushes the value of the double local variable at index 0 and 1 in the current method frame on the stack.
dload_1 39 0 Pushes the value of the double local variable at index 1 and 2 in the current method frame on the stack.
dload_2 40 0 Pushes the value of the double local variable at index 2 and 3 in the current method frame on the stack.
dload_3 41 0 Pushes the value of the double local variable at index 3 and 4 in the current method frame on the stack.
aload_0 42 0 Pushes the value of the object reference local variable at index 0 in the current method frame on the stack.
aload_1 43 0 Pushes the value of the object reference local variable at index 1 in the current method frame on the stack.
aload_2 44 0 Pushes the value of the object reference local variable at index 2 in the current method frame on the stack.
aload_3 45 0 Pushes the value of the object reference local variable at index 3 in the current method frame on the stack.
istore 45 1 Pops the integer value from the stack and stores it into the local variable at the index specified by the argument in the current method frame.
iaload 46 0 Pops an array index and an integer array object reference off the stack and pushes the element at index back onto the stack.
laload 47 0 Pops an array index, and a long array object reference off the stack and pushes the element at index back onto the stack.
faload 48 0 Pops an array index and a float array object reference off the stack and pushes the element at index back onto the stack.
daload 49 0 Pops an array index and a double array object reference off the stack and pushes the element at index back onto the stack.
aaload 50 0 Pops an array index and an object reference array object reference off the stack and pushes the element at index back onto the stack.
baload 51 0 Pops an array index and a signed byte array object reference off the stack and pushes the element at index back onto the stack.
caload 52 0 Pops an array index and a char array object reference off the stack and pushes the element at index back onto the stack.
saload 53 0 Pops an array index and a short array object reference off the stack and pushes the element at index back onto the stack.
lstore 55 1 Pops the long value from the stack and stores it in the local variable at index and index+1 specified by the argument in the current method frame.
fstore 56 1 Pops the float value from the stack and stores it in the local variable at the index specified by the argument in the current method frame.
dstore 57 1 Pops the double value from the stack and stores it in the local variable at index and index+1 specified by the argument in the current method frame.
astore 58 1 Pops the object reference from the stack and stores it in the local variable at the index specified by the argument in the current method frame.
istore_0 59 0 Pops the integer value from the stack and stores it in the local variable at index 0 in the current method frame.
istore_1 60 0 Pops the integer value from the stack and stores it in the local variable at index 1 in the current method frame.
istore_2 61 0 Pops the integer value from the stack and stores it in the local variable at index 2 in the current method frame.
istore_3 62 0 Pops the integer value from the stack and stores it in the local variable at index 3 in the current method frame.
lstore_0 63 0 Pops the long value from the stack and stores it in the local variable at index 0 and 1 in the current method frame.
lstore_1 64 0 Pops the long value from the stack and stores it in the local variable at index 1 and 2 in the current method frame.
lstore_2 65 0 Pops the long value from the stack and stores it in the local variable at index 2 and 3 in the current method frame.
lstore_3 66 0 Pops the long value from the stack and stores it in the local variable at index 3 and 4 in the current method frame.
fstore_0 67 0 Pops the float value from the stack and stores it in the local variable at index 0 in the current method frame.
fstore_1 68 0 Pops the float value from the stack and stores it in the local variable at index 1 in the current method frame.
fstore_2 69 0 Pops the float value from the stack and stores it in the local variable at index 2 in the current method frame.
fstore_3 70 0 Pops the float value from the stack and stores it in the local variable at index 3 in the current method frame.
dstore_0 71 0 Pops the double value from the stack and stores it in the local variable at index 0 and 1 in the current method frame.
dstore_1 72 0 Pops the double value from the stack and stores it in the local variable at index 1 and 2 in the current method frame.
dstore_2 73 0 Pops the double value from the stack and stores it in the local variable at index 2 and 3 in the current method frame.
dstore_3 74 0 Pops the double value from the stack and stores it in the local variable at index 3 and 4 in the current method frame.
astore_0 75 0 Pops the object reference from the stack and stores it in the local variable at index 0 in the current method frame.
astore_1 76 0 Pops the object reference from the stack and stores it in the local variable at index 1 in the current method frame.
astore_2 77 0 Pops the object reference from the stack and stores it in the local variable at index 2 in the current method frame.
astore_3 78 0 Pops the object reference from the stack and stores it in the local variable at index 3 in the current method frame.
iastore 79 0 Pops an integer value, an array index, and an integer array object reference off the stack and stores the integer value in the array element at index.
lastore 80 0 Pops a long value, an array index, and a long array object reference off the stack and stores the long value in the array element at index.
fastore 81 0 Pops a float value, an array index, and a float array object reference off the stack and stores the float value in the array element at index.
dastore 82 0 Pops a double value, an array index, and a double array object reference off the stack and stores the double value in the array element at index.
aastore 83 0 Pops an object reference, an array index, and an object reference array object reference off the stack and stores the object reference in the array element at index.
bastore 84 0 Pops a signed byte value, an array index, and a signed byte array object reference off the stack and stores the signed byte value in the array element at index.
castore 85 0 Pops a char value, an array index, and a char array object reference off the stack and stores the char value in the array element at index.
sastore 86 0 Pops a short value, an array index, and a short array object reference off the stack and stores the short value in the array element at index.
pop 87 0 Pops the word from the top of the stack.
pop2 88 0 Pops two words from the top of the stack.
dup 89 0 Duplicates the word at the top of the stack.
dup_x1 90 0 Duplicates the word at the top of the stack and puts the duplicate value two words down.
dup_x2 91 0 Duplicates the word at the top of the stack and puts the duplicate value three words down.
dup2 92 0 Duplicates the two words at the top of the stack.
dup2_x1 93 0 Duplicates the two words at the top of the stack and puts the duplicate values two words down.
dup2_x2 94 0 Duplicates the two words at the top of the stack and puts the duplicate value three words down.
swap 95 0 Swaps the two words at the top of the stack.
iadd 96 0 Pops the two integer values off the stack, adds them, and pushes the result on top of the stack.
ladd 97 0 Pops the two long values off the stack, adds them, and pushes the result on top of the stack.
fadd 98 0 Pops the two float values off the stack, adds them, and pushes the result on top of the stack.

dadd 99 0 Pops the two double values off the stack, adds them, and pushes the result on top of the stack.
isub 100 0 Pops the two integer values off the stack, subtracts them, and pushes the result on top of the stack.
lsub 101 0 Pops the two long values off the stack, subtracts them, and pushes the result on top of the stack.
fsub 102 0 Pops the two float values off the stack, subtracts them, and pushes the result on top of the stack.
dsub 103 0 Pops the two double values off the stack, subtracts them, and pushes the result on top of the stack.
imul 104 0 Pops the two integer values off the stack, multiplies them, and pushes the result on top of the stack.
lmul 105 0 Pops the two long values off the stack, multiplies them, and pushes the result on top of the stack.
>fmul 106 0 Pops the two float values off the stack, multiplies them, and pushes the result on top of the stack.
dmul 107 0 Pops the two double values off the stack, multiplies them, and pushes the result on top of the stack.
idiv 108 0 Pops the two integer values off the stack, divides them, and pushes the result on top of the stack.
ldiv 109 0 Pops the two long values off the stack, divides them, and pushes the result on top of the stack.
fdiv 110 0 Pops the two float values off the stack, divides them, and pushes the result on top of the stack.
ddiv 111 0 Pops the two double values off the stack, divides them, and pushes the result on top of the stack.
irem 112 0 Pops the two integer values off the stack, divides them, and pushes the remainder on top of the stack.
lrem 113 0 Pops the two long values off the stack, divides them, and pushes the remainder on top of the stack.
frem 114 0 Pops the two float values off the stack, divides them, and pushes the remainder on top of the stack.
drem 115 0 Pops the two double values off the stack, divides them, and pushes the remainder on top of the stack.
ineg 116 0 Pops the integer value off the stack, calculates its arithmetic negation, and pushes the result on top of the stack.
lneg 117 0 Pops the long value off the stack, calculates its arithmetic negation, and pushes the result on top of the stack.
fneg 118 0 Pops the float value off the stack, calculates its arithmetic negation, and pushes the result on top of the stack.
dneg 119 0 Pops the double value off the stack, calculates its arithmetic negation, and pushes the result on top of the stack.
ishl 120 0 Pops the shift count and integer value, shifts the value left by the low five bits of the shift count, and pushes the integer result on top of the stack.
lshl 121 0 Pops the shift count and the long value, shifts the value left by the low six bits of the shift count, and pushes the long result on top of the stack.
ishr 122 0 Pops the shift count and the integer value, arithmetically shifts the value right (extending the sign) by the low five bits of the shift count, and pushes the integer result on top of the stack.
lshr 123 0 Pops the shift count and the long value, arithmetically shifts the value right (extending the sign) by the low six bits of the shift count, and pushes the long result on top of the stack.
iushr 124 0 Pops the shift count and the integer value, logically shifts the value right (not extending the sign) by the low five bits of the shift count, and pushes the integer result on top of the stack.
iushr 125 0 Pops the shift count and the long value, logically shifts the value right (not extending the sign) by the low six bits of the shift count, and pushes the long result on top of the stack.
iand 126 0 Pops two integer values off the stack, performs a bitwise, and then puts the result back on top of the stack.
land 127 0 Pops two long values off the stack, performs a bitwise, and then puts the result back on top of the stack.
ior 128 0 Pops two integer values off the stack, performs a bitwise, or then puts the result back on top of the stack.
lor 129 0 Pops two long values off the stack, performs a bitwise, or then puts the result back on top of the stack.
ixor 130 0 Pops two integer values off the stack, performs a bitwise x, or then puts the result back on top of the stack.
lxor 131 0 Pops two long values off the stack, performs a bitwise x, or then puts the result back on top of the stack.
iinc 132 2 Increments the integer local variable at index (arg1) in the current method frame by the signed 8-bit value in (arg2).
i2l 133 0 Pops an integer value off the stack, converts it to a long, and pushes it on the stack.
i2f 134 0 Pops an integer value off the stack, converts it to a float, and pushes it on the stack.
i2d 135 0 Pops an integer value off the stack, converts it to a double, and pushes it on the stack.
l2i 136 0 Pops a long value off the stack, converts it to an integer, and pushes it on the stack.
l2f 137 0 Pops a long value off the stack, converts it to a float, and pushes it on the stack.
l2d 138 0 Pops a long value off the stack, converts it to a double, and pushes it on the stack.
f2i 139 0 Pops a float value off the stack, converts it to an integer, and pushes it on the stack.
f2l 140 0 Pops a float value off the stack, converts it to a long, and pushes it on the stack.
f2d 141 0 Pops a float value off the stack, converts it to a double, and pushes it on the stack.
d2i 142 0 Pops a double value off the stack, converts it to an integer, and pushes it on the stack.
d2l 143 0 Pops a double value off the stack, converts it to a long, and pushes it on the stack.
d2f 144 0 Pops a double value off the stack, converts it to a float, and pushes it on the stack.
int2byte 145 0 Pops an integer value off the stack, converts it to a signed byte, and pushes it on the stack.
int2char 146 0 Pops an integer value off the stack, converts it to a char, and pushes it on the stack.
int2short 147 0 Pops an integer value off the stack, converts it to a short, and pushes it on the stack.
lcmp 148 0 Pops long value2 and long value1 from the stack. If value1 is greater than value2, then push integer 1 on the stack. If value1 equals value2, then push integer 0 on the stack. If value1 is less than value2, then push integer -1 on the stack.
fcmpl 149 0 Pops float value2 and float value1 from the stack. If value1 is greater than value2, then push integer 1 on the stack. If value1 equals value2, then push integer 0 on the stack. If value1 is less than value2 or either value is NaN, then push integer -1 on the stack.
fcmpg 150 0 Pops float value2 and float value1 from the stack. If value1 is greater than value2, then push integer 1 on the stack. If value1 equals value2, then push integer 0 on the stack. If value1 is less than value2 or either value is NaN, then push integer 1 on the stack.
dcmpl 151 0 Pops double value2 and double value1 from the stack. If value1 is greater than value2, then push integer 1 on the stack. If value1 equals value2, then push integer 0 on the stack. If value1 is less than value2 or either value is NaN, then push integer -1 on the stack.
dcmpg 152 0 Pops double value2 and double value1 from the stack. If value1 is greater than value2, then push integer 1 on the stack. If value1 equals value2, then push integer 0 on the stack. If value1 is less than value2 or either value is NaN, then push integer 1 on the stack.
ifeq 153 2 Pops an integer value off the stack. If it is equal to zero, then the two args are added together and added to the current Program Counter; otherwise, the next instruction is executed.
ifne 154 2 Pops an integer value off the stack. If it is not equal to 0, then the two args are added together and added to the current Program Counter; otherwise, the next instruction is executed.
iflt 155 2 Pops an integer value off the stack. If it is less than zero, then the two args are added together and added to the current Program Counter; otherwise, the next instruction is executed.
ifge 156 2 Pops an integer value off the stack. If it is greater than or equal to 0, then the two args are added together and added to the current Program Counter; otherwise, the next instruction is executed.
ifgt 157 2 Pops an integer value off the stack. If it is greater than 0, then the two args are added together and added to the current Program Counter; otherwise, the next instruction is executed.
ifle 158 2 Pops an integer value off the stack. If it is less than or equal to 0, then the two args are added together and added to the current Program Counter; otherwise, the next instruction is executed.
if_icmpeq 159 2 Pops integer value2 and integer value1 from the stack. If value1 equals value2, then the two args are added together and added to the current Program Counter; otherwise, the next instruction is executed.
if_icmpne 160 2 Pops integer value2 and integer value1 from the stack. If value1 is not equal to value2, then the two args are added together and added to the current Program Counter; otherwise, the next instruction is executed.
if_icmplt 161 2 Pops integer value2 and integer value1 from the stack. If value1 is less than value2 then the two args are added together and added to the current Program Counter; otherwise the next instruction is executed.
if_icmpge 162 2 Pops integer value2 and integer value1 from the stack. If value1 is greater than or equal to value2, then the two args are added together and added to the current Program Counter; otherwise, the next instruction is executed.
if_icmpgt 163 2 Pops integer value2 and integer value1 from the stack. If value1 is greater than value2, then the two args are added together and added to the current Program Counter; otherwise, the next instruction is executed.
if_icmple 164 2 Pops integer value2 and integer value1 from the stack. If value1 is less than or equal to value2, then the two args are added together and added to the current Program Counter; otherwise, the next instruction is executed.
if_acmpeq 165 2 Pops object reference value2 and object reference value1 from the stack. If the values refer to the same object, then the two args are added together and added to the current Program Counter; otherwise, the next instruction is executed.
if_acmpne 166 2 Pops object reference value2 and object reference value1 from the stack. If the values do not refer to the same object, then the two args are added together and added to the current Program Counter; otherwise, the next instruction is executed.
goto 167 2 Adds the two args together, constructing a 16-bit value, and adds to the current Program Counter.
jsr 168 2 Adds the two args together, constructing a 16-bit integer value. Pushes the Program Counter location of the instruction immediately following this one onto the stack. Adds the 16-bit value to the Program Counter to move the flow of execution to the subroutine. At the entry to the subroutine, the return address is popped off the stack and saved in a local variable for later use in the ret and ret_w instructions. (This instruction is used when the Java VM processes a finally block.)
ret 169 1 Uses the argument as an index into the method’s frame to a local variable that contains the return address of the caller. The return address is then put into the Program Counter to move the flow of execution back to the caller of this subroutine. (This instruction is used when the JVM processes a finally block.)
tableswitch 170 >12 Represents the compiled implementation of a switch statement where the location of the desired case is on the stack. After the OpCode, there may be 0 to 3 bytes of padding in order to bring the next arguments to a 4-byte boundary. The next three arguments help describe the size of the table. After the pad bytes is a 32-bit integer representing the offset into the table for the default block. Then follows two 32-bit values representing the lowest and highest allowable index values, respectively. Next is the actual table. The table is an array of 32-bit integers containing the offsets from the beginning of this instruction to the block of code for a case in the switch statement. There are (high-index - low-index + 1) 32-bit entries in the table, with the first entry considered to be at offset zero. The index to be used in the actual lookup is an integer that must be popped off the stack. If the index value is not in the range [low-index, high-index], then the address for the default block is used. Otherwise, the value of low-index is subtracted from the index off the stack to determine the table slot containing the new offset where the execution point should be moved.
lookupswitch 171 >12 Represents the compiled implementation of a switch statement that is based on determining the index by matching up an integer key, which is located on the stack with a value in the table. After the OpCode, there may be 0 to 3 bytes of padding in order to bring the next arguments to a 4-byte boundary. The next two arguments help describe the size of the table. After the pad bytes is a 32-bit integer representing the offset into the table for the default block. Then follows a 32-bit value representing the number of match/offset pairs that make up the table elements. Next is the actual table. The table is an array of 32-bit integer pairs containing a value to compare the key with and the offset from the beginning of this instruction to the block of code for a matching case in the switch statement. The key to be used in the actual match is an integer that must be popped off the stack. If the key does not match any of the entries in the table, then the address for the default block is used. Otherwise, the index value of the matching table entry is added to the Program Counter. Execution continues from that point.
ireturn 172 0 Pops an integer value from the current method’s stack. This integer value is then pushed onto the stack of the caller’s method frame. Control is then returned to the caller’s method.
lreturn 173 0 Pops a long value from the current method’s stack. This integer value is then pushed onto the stack of the caller’s method frame. Control is then returned to the caller’s method.
freturn 174 0 Pops a float value from the current method’s stack. This integer value is then pushed onto the stack of the caller’s method frame. Control is then returned to the caller’s method.
dreturn 175 0 Pops a double value from the current method’s stack. This integer value is then pushed onto the stack of the caller’s method frame. Control is then returned to the caller’s method.
areturn 176 0 Pops an object reference value from the current method’s stack. This integer value is then pushed onto the stack of the caller’s method frame. Control is then returned to the caller’s method.
return 177 0 Control is returned to the caller’s method without pushing any result value onto the caller’s stack.
getstatic 178 2 Gets a value from a class’s static field. The arguments are added together to create 16-bit offset into the constant pool to a Field Reference entry. The class and field are resolved, and the size of the value and its offset into the class are determined. Based on the knowledge of its size, the value is retrieved from the class’s static field area and pushed on top of the stack.
putstatic 179 2 Puts a value into a class’s static field. The arguments are added together to create 16-bit offset into the constant pool to a Field Reference entry. The class and field are resolved, and the size of the value and its offset into the class are determined. Based on the knowledge of its size, the value is popped from the stack. The value is then placed into the class’s static field area at the offset determined from the field information.
putfield 181 2 Puts a value into an object’s nonstatic field. The arguments are added together to create 16-bit offset into the Constant Pool to a Field Reference entry. The class and field are resolved, and the size of the value and its offset into the object are determined. Based on the knowledge of its size, the value is first popped from the stack, followed by the actual object reference. The value is then placed into the object reference at the offset determined from the field information.
getfield 182 2 Gets a value from an object’s nonstatic field. The arguments are added together to create 16-bit offset into the constant pool to a Field Reference entry. The class and field are resolved, and the size of the value and its offset into the object are determined. Next, the actual object reference is popped off the stack. The value is then retrieved from the object reference at the offset determined from the field information and pushed on top of the stack.
invokevirtual 182 2 Invokes an instance method of the object reference on the stack based on dynamic type lookup. The arguments are added together to create a 16-bit offset into the Constant Pool to a Method Reference entry. The class, method signature, and location of the method’s bytecodes are resolved dynamically to determine the number of arguments and the sizes that need to be popped off the stack. Next, the arguments are popped off the stack, followed by the object reference of the class containing the method to be called. The object reference and arguments (in that order) become the first local variables in the new frame that is created for the method to be called. Finally, control is passed to the method.
Invokenonvirtual 183 2 Invokes an instance method of the object reference on the stack based on compile-time type lookup. Logic is identical to invokevirtual, except that the class information has already resolved.
invokestatic 184 2 Invokes a class’s static method. Logic is similar to invokenonvirtual, except that there is no object reference behind the arguments on the stack (as static methods don’t require an object of this type to be instantiated).
invokeinterface 185 4 Invokes an object’s interface method. Logic is similar to invokevirtual, except that the number of arguments to the method is present as the third argument of the OpCode. The fourth argument is reserved and not used.
new 187 2 Creates a new object based on the class type defined by the arguments. The arguments are added together to create a 16-bit Constant Pool index to a Class Reference entry. The class information is resolved, and a new object reference is created for the class. The object reference is then pushed on the top of the stack.
newarray 188 1 Allocates a new array containing elements from one of the Java native data types. The number of elements to allocate is on the stack at entry to this OpCode. The argument to this OpCode may be one of the following type designators: boolean, 4; char, 5; float, 6; double, 7; byte, 8; short, 9; int, 10; long, 11.
anewarray 189 2 Allocates a new array containing elements of object references. The number of elements to allocate is on the stack at entry to this OpCode. The arguments to this OpCode, when added together, make up a 16-bit Constant Pool index to the class type that will be referenced by the array elements.
athrow 191 0 Throws an exception. The top of the stack must contain an object reference that is subclassed from Throwable. The specified exception object is popped off the stack and thrown. The process of throwing an exception requires the current method’s frame to be searched for an appropriate exception handler. If one is found, the Program Counter is set to the address of the first bytecode of the handler. Otherwise, this method frame is popped, and the exception is rethrown to the caller of this method.
checkcast 192 2 Verifies that a cast operation is valid given the type of object reference on the top of the stack. The arguments are added together to create a 16-bit Constant Pool index to a Class Reference entry. The class information is resolved. The type of the object reference on the top of the stack is compared to the type of class specified by the Constant Pool entry. If the object on the stack is an instance of the class found in the Constant Pool or one of its super classes, then execution continues with the next instruction. Otherwise, a ClassCastException is thrown.
instanceof 193 2 Verifies that an object is of the specified type based on the arguments. The arguments are added together to create a 16-bit Constant Pool index to a Class Reference entry. The class information is resolved and the object reference is popped off the stack. The type of the object is compared to the type of class specified by the Constant Pool entry. If the object on the stack is an instance of the class found in the Constant Pool or one of its super classes, then the integer value 1 is pushed on the stack. Otherwise, the value 0 is pushed on the stack.
monitorenter 194 0 Enters a monitored section of the current bytecode stream and pops the object reference off the top of the stack. Try to allocate an exclusive lock on the object reference. If another monitor already has this object locked, than wait for it to become unlocked. If the object is already locked, then just continue. Otherwise, allocate a new exclusive lock on the object.
monitorexit 195 0 Leaves a monitored section of the current bytecode stream and pops the object reference off the top of the stack. The exclusive lock on the object reference is removed. If no other threads have this object locked, then any other threads waiting for this object are notified that the object is now available.
wide 196 1 Provides for a 16-bit index in local variable load, store, and increment OpCodes. This is possible by adding the 8-bit quantity in (arg1) to the index in the argument of the succeeding OpCode in the bytecode stream that follows this one.
multianewarray 197 3 Allocates a new multidimensional array containing elements of object references. The number of elements to allocate per dimension are on the stack at the entry to this OpCode. The first two arguments to this OpCode, when added together, make up a 16-bit Constant Pool index to the class type that will be referenced by the array elements. The third argument is the number of dimensions that the array is to contain.
ifnull 198 2 Pops an object reference off the stack. If it is null, then the two args are added together and added to the current Program Counter. Otherwise, the next instruction is executed.
ifnonnull 199 2 Pops an object reference off the stack. If it is not null, then the two args are added together and added to the current Program Counter. Otherwise, the next instruction is executed.
goto_2 200 4 Adds the four args together, constructing a 32-bit value, and adds to the current Program Counter.

jsr_w 201 4 Adds the four args together, constructing a 32-bit integer value. Push the Program Counter location of the instruction immediately following this one onto the stack. At entry to the subroutine, the return address is popped off the stack and saved in a local variable for later use in the ret and ret_w instructions. Add the 32-bit value to the Program Counter to move the flow of execution to the subroutine. (This instruction is used when the Java VM processes a finally block.)
breakpoint 202 0 Stops execution and passes control to the JVM’s breakpoint handler.
ret_w 209 2 Adds the two arguments together to create a 16-bit index into the method’s frame to a local variable that contains the return address of the caller. The return address is then put into the Program Counter to move the flow of execution back to the caller of this subroutine. (This instruction is used when the JVM processes a finally block.)

Previous Page TOC Next Page

| Previous Chapter | Next Chapter |

|Table of Contents | Book Home Page |

| Que Home Page | Digital Bookshelf | Disclaimer |


To order books from QUE, call us at 800-716-0044 or 317-361-5400.

For comments or technical support for our books and software, select Talk to Us.

© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company