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 PCs 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 isnt it still around today? When I asked Suns 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.
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 Suns 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 Suns initial specification for the .class file structure, bytecode definitions, and 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 computers 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 countera 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.
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, lets examine how it deals with memory management.
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 applications 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 objects 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 objects 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, lets 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 objects 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 dont 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 JVMs 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 methodresurrect 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.
Thats 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 dont 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().
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 dont 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.
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 usedthe 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 methods 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 methods stack. This integer value is then pushed onto the stack of the callers method frame. Control is then returned to the callers method. |
lreturn | 173 | 0 | Pops a long value from the current methods stack. This integer value is then pushed onto the stack of the callers method frame. Control is then returned to the callers method. |
freturn | 174 | 0 | Pops a float value from the current methods stack. This integer value is then pushed onto the stack of the callers method frame. Control is then returned to the callers method. |
dreturn | 175 | 0 | Pops a double value from the current methods stack. This integer value is then pushed onto the stack of the callers method frame. Control is then returned to the callers method. |
areturn | 176 | 0 | Pops an object reference value from the current methods stack. This integer value is then pushed onto the stack of the callers method frame. Control is then returned to the callers method. |
return | 177 | 0 | Control is returned to the callers method without pushing any result value onto the callers stack. |
getstatic | 178 | 2 | Gets a value from a classs 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 classs static field area and pushed on top of the stack. |
putstatic | 179 | 2 | Puts a value into a classs 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 classs static field area at the offset determined from the field information. |
putfield | 181 | 2 | Puts a value into an objects 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 objects 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 methods 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 classs static method. Logic is similar to invokenonvirtual, except that there is no object reference behind the arguments on the stack (as static methods dont require an object of this type to be instantiated). |
invokeinterface | 185 | 4 | Invokes an objects 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 methods 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 JVMs breakpoint handler. |
ret_w | 209 | 2 | Adds the two arguments together to create a 16-bit index into the methods 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 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