Chapter 21

Under the Hood


CONTENTS


On today, your final day, the inner workings of the Java system will be revealed.

You'll find out all about Java's vision, Java's virtual machine, those bytecodes you've heard so much about, that mysterious garbage collector, and why you might worry about security but don't have to.

We'll begin, however, with the big picture.

The Big Picture

The Java team is very ambitious. Their ultimate goal is nothing less than to revolutionize the way software is written and distributed. They've started with the Internet, where they believe much of the interesting software of the future will live.

To achieve such an ambitious goal, a large fraction of the Internet programming community itself must be marshaled behind a similar goal and given the tools to help achieve it. The Java language, with its four S's (small, simple, safe, secure), and its flexible, Net-oriented environment, is intended to become the focal point for the rallying of a new legion of programmers.

To this end, Sun Microsystems has done something rather gutsy. What was originally a secret, tens-of-millions-of-dollars research and development project, and 100 percent proprietary, has become a free, open, and relatively unencumbered technology standard upon which anyone can build. The company is literally giving it away and reserving only the rights it needs to maintain and grow the standard.

Note
Actually, as Sun's lawyers have had more time to think, the original intentions of the Java team get further obscured by legal details. It still is relatively unencumbered, but its earlier releases were completely unencumbered. Let's hope that this is not a pattern that will continue.

Any truly open standard must be supported by at least one excellent, freely available "demonstration" implementation. In parallel, several universities, companies, and individuals already have expressed their intention to duplicate the Java environment, based on the open API that Sun has created.

Several other languages even are contemplating compiling down to Java bytecodes, to help support it in becoming a more robust and widespread standard for moving executable content around on the Net.

Why It's a Powerful Vision

One of the reasons this brilliant move on Sun's part has a real chance of success is the pent-up frustration of a whole generation of programmers who want to share their code to be usable on many machines regardless of platform. Right now, the computer science world is balkanized into factions at universities and companies all over the world, with hundreds of languages, dozens of them widely used, dividing and separating us all. It's the worst sort of Tower of Babel. Java hopes to build some bridges and help tear down that tower. Because it is so simple, because it's so useful for programming over the Internet, and because the Internet is so "hot" right now-this confluence of forces should help propel Java onto center stage.

Java deserves to be there. It is the natural outgrowth of ideas that, since the early 1970s inside the Smalltalk group at Xerox PARC, have lain relatively dormant in the mainstream. Smalltalk, in fact, invented the first object-oriented bytecode interpreter and pioneered many of the deep ideas that Java builds on today. Those efforts were not embraced over the intervening decades as a solution to the general problems of software, however. Today, with those problems becoming so much more obvious, and with the Net crying out for a new kind of programming, the soil is fertile to grow something stronger from those old roots, something that just might spread like wildfire. (Is it a coincidence that Java's previous internal names were Green and OAK?)

This new vision of software is one in which the Net becomes a heterogeneous grouping of objects, classes, and the open APIs between them. Traditional applications have vanished, replaced by skeletal frameworks that can be fitted with any parts from this grouping, on demand, to suit any purpose. User interfaces will be mixed and matched, built in pieces and constructed to taste, whenever the need arises, by their own users. Menus of choices will be filled by dynamic lists of all of the choices available for that function, at that exact moment, across the entire Net.

These frameworks will come into existence to support entertainment, business, and the social (cyber-)spaces of the near future.

This is a dream that many of us have waited all our lives to be a part of. There are tremendous challenges to making it all come true, but the powerful winds of change we all feel must stir us into action, because, at last, there is a base on which to build that dream-Java.

The Java Virtual Machine

To make visions like this possible, Java must be ubiquitous. It must be able to run on any computer and any operating system, now and in the future. In order to achieve this level of portability, Java must be very precise not only about the language itself, but about the environment in which the language lives. You can see, from earlier in the book and appendix B, that the Java environment includes a generally useful set of packages of classes and a freely available implementation of them. This takes care of a portion of what is needed, but it also is crucial to specify exactly how the runtime environment of Java behaves.

This final requirement is what has stymied many attempts at ubiquity in the past. If you base your system on any assumptions about what is "beneath" the runtime system, you lose. If you depend in any way on the computer or operating system below, you lose. Java solves this problem by inventing an abstract computer of its own and running on that.

This "virtual" machine runs a special set of "instructions" called bytecodes that are simply a stream of formatted bytes, each of which has a precise specification that defines exactly what each bytecode does to this virtual machine. The virtual machine also is responsible for certain fundamental capabilities of Java, such as object creation and garbage collection.

Finally, in order to be able to move bytecodes safely across the Internet, you need a bulletproof model of security-and how to maintain it-and a precise format for how this stream of bytecodes can be sent from one virtual machine to another.

Each of these requirements is addressed in today's lesson.

Note
This discussion blurs the distinction between the terms runtime and the virtual machine of Java. This is intentional but a little unconventional. Think of the virtual machine as providing all of the capabilities, even those that are conventionally assigned to the runtime. This book uses the words "runtime" and "virtual machine" interchangeably. Equating the two highlights the single environment that must be created to support Java.
Much of the following description is based closely on the latest "Virtual Machine Specifications" documents (and the 1.0 bytecodes), so if you delve more deeply into the details online at Sun's site (http://www.javasoft.com), you should cover some familiar ground.

An Overview

It is worth quoting the introduction to the Java virtual machine documentation here, because it is so relevant to the vision outlined earlier:

The Java virtual machine specification has a purpose that is both like and unlike equivalent documents for other languages and abstract machines. It is intended to present an abstract, logical machine design free from the distraction of inconsequential details of any implementation. It does not anticipate an implementation technology or an implementation host. At the same time it gives a reader sufficient information to allow implementation of the abstract design in a range of technologies.
However, the intent of the…Java project is to create a language…that will allow the interchange over the Internet of "executable content," which will be embodied by compiled Java code. The project specifically does not want Java to be a proprietary language and does not want to be the sole purveyor of Java language implementations. Rather, we hope to make documents like this one, and source code for our implementation, freely available for people to use as they choose.
This vision…can be achieved only if the executable content can be reliably shared between different Java implementations. These intentions prohibit the definition of the Java virtual machine from being fully abstract. Rather, relevant logical elements of the design have to be made sufficiently concrete to allow the interchange of compiled Java code. This does not collapse the Java virtual machine specification to a description of a Java implementation; elements of the design that do not play a part in the interchange of executable content remain abstract. But it does force us to specify, in addition to the abstract machine design, a concrete interchange format for compiled Java code.

The Java virtual machine specification consists of the following:

Endian-ness is the arrangement of bytes inside a value. The value must contain more than one byte.

Each of these is covered today.

Despite this degree of specificity, there are still several elements of the design that remain purposely abstract, including the following:

These places are where the creativity of a virtual machine implementor has full rein.

The Fundamental Parts

The Java virtual machine can be divided into five fundamental pieces:

Some of these might be implemented by using an interpreter, a native binary code compiler, or even a hardware chip-but all these logical, abstract components of the virtual machine must be supplied in some form in every Java system.

Note
The memory areas used by the Java virtual machine are not required to be at any particular place in memory, to be in any particular order, or even to use contiguous memory. However, all but the method area must be able to represent aligned 32-bit values (for example, the Java stack is 32 bits wide).

The virtual machine and its supporting code are often referred to as the runtime environment. When this book refers to something being done at runtime, the virtual machine is what's doing it.

Java Bytecodes

The Java virtual machine instruction set is optimized to be small and compact. It is designed to travel across the Net, and so has traded off speed-of-interpretation for space. (Given that both Net bandwidth and mass storage speeds increase less rapidly than CPU speed, this seems like an appropriate trade-off.)

As mentioned, Java source code is "compiled" into bytecodes and stored in a .class file. On Sun's Java system, this is performed using the javac tool. It is not exactly a traditional compiler, because javac translates source code into bytecodes, a lower-level format that cannot be run directly, but must be further interpreted by each computer. Of course, it is exactly this level of "indirection" that buys you the power, flexibility, and extreme portability of Java code.

Note
Quotation marks are used around the word "compiler" when talking about javac or the Café native compiler because later today you also will learn about the "just-in-time" compiler, which acts more like the back end of a traditional compiler. The use of the same word "compiler" for these two different pieces of Java technology is unfortunate, but somewhat reasonable, because each is really one-half (either the front or the back end) of a more traditional compiler.

A bytecode instruction consists of a one-byte opcode that serves to identify the instruction involved and zero or more operands, each of which may be more than one byte long, that encode the parameters the opcode requires.

Note
When operands are more than one byte long, they are stored in big-endian order, high-order byte first. These operands must be assembled from the byte stream at runtime. For example, a 16-bit parameter appears in the stream as two bytes so that its value is first_byte * 256 + second_byte. The bytecode instruction stream is only byte-aligned, and alignment of any larger quantities is not guaranteed (except for within the special bytecodes lookupswitch and tableswitch, which have special alignment rules of their own).

Bytecodes interpret data in the runtime memory areas as belonging to a fixed set of types: the primitive types you've seen several times before, consisting of several signed integer types (8-bit byte, 16-bit short, 32-bit int, 64-bit long), one unsigned integer type (16-bit char), and two signed floating-point types (32-bit float, 64-bit double), plus the type "reference to an object" (a 32-bit pointer-like type). Some special bytecodes (for example, the dup instructions), treat runtime memory areas as raw data, without regard to type. This is the exception, however, not the rule.

These primitive types are distinguished and managed by the compiler, javac, not by the Java runtime environment. These types are not "tagged" in memory, and thus cannot be distinguished at runtime. Different bytecodes are designed to handle each of the various primitive types uniquely, and the compiler carefully chooses from this palette based on its knowledge of the actual types stored in the various memory areas. For example, when adding two integers, the compiler generates an iadd bytecode; for adding two floats, fadd is generated. (You'll see all this in gruesome detail later.)

Registers

The registers of the Java virtual machine are just like the registers inside a "real" computer.

Registers hold the machine's state, affect its operation, and are updated after each bytecode is executed.

The following are the Java registers:

The virtual machine defines these registers to be 32 bits wide.

Note
Because the virtual machine is primarily stack-based, it does not use any registers for passing or receiving arguments. This is a conscious choice skewed toward bytecode simplicity and compactness. It also aids efficient implementation on register-poor architectures, which most of today's computers are, unfortunately. Perhaps when the majority of CPUs out there are a little more sophisticated, this choice will be re-examined, though simplicity and compactness might still be reason enough!
By the way, the pc register also is used when the runtime handles exceptions; catch clauses ultimately are associated with ranges of the pc within a method's bytecodes.

The Stack

The Java virtual machine is stack-based. A Java stack frame is similar to the stack frame of a conventional programming language-it holds the state for a single method call. Frames for nested method calls are stacked on top of this frame.

The stack is used to supply parameters to bytecodes and methods, and to receive results back from them.

Each stack frame contains three possibly empty sets of data: the local variables for the method call, its execution environment, and its operand stack. The sizes of these first two are fixed at the start of a method call, but the operand stack varies in size as bytecodes are executed in the method.

Local variables are stored in an array of 32-bit slots, indexed by the register vars. Most types take up one slot in the array, but the long and double types each take up two slots.

Note
Long and double values, stored or referenced through an index N, take up the 32-bit slots[]and[]+ 1. These 64-bit values are thus not guaranteed to be 64-bit-aligned. Implementors are free to decide the appropriate way to divide these values among the two slots.

The execution environment in a stack frame helps to maintain the stack itself. It contains a pointer to the previous stack frame, a pointer to the local variables of the method call, and pointers to the stack's current "base" and "top." Additional debugging information also can be placed into the execution environment.

The operand stack, a 32-bit first-in-first-out (FIFO) stack, is used to store the parameters and return values of most bytecode instructions. For example, the iadd bytecode expects two integers to be stored on the top of the stack. It pops them, adds them together, and pushes the resulting sum back onto the stack.

Each primitive data type has unique instructions that know how to extract, operate, and push back operands of that type. For example, long and double operands take two "slots" on the stack, and the special bytecodes that handle these operands take this into account. It is illegal for the types on the stack and the instruction operating on them to be incompatible (javac outputs bytecodes that always obey this rule).

Note
The top of the operand stack and the top of the overall Java stack are almost always the same. Thus, "the stack" refers to both stacks, collectively.

The Heap

The heap is that part of memory from which newly created instances (objects) are allocated.

The heap often is assigned a large, fixed size when the Java runtime system is started, but on systems that support virtual memory, it can grow as needed, in a nearly unbounded fashion.

Because objects automatically are garbage-collected in Java, programmers do not have to (and, in fact, cannot) manually free the memory allocated to an object when they are finished using it.

Java objects are referenced indirectly in the runtime, through handles, which are a kind of pointer into the heap.

Because objects are never referenced directly, parallel garbage collectors can be written that operate independently of your program, moving around objects in the heap at will. You'll learn more about garbage collection later.

The Method Area

Like the compiled code areas of conventional programming language environments, or the TEXT segment in a UNIX process, the method area stores the Java bytecodes that implement almost every method in the Java system. (Remember that some methods might be native, and thus implemented-for example, in C.) The method area also stores the symbol tables needed for dynamic linking, and any other additional information debuggers or development environments which might want to associate with each method's implementation.

Note
A native method is just like anything else that is called native in Java. It means that the code outside of the Java language is involved. The term native method refers to a way to call code written in other languages (usually C) from your Java applications.

Because bytecodes are stored as byte streams, the method area is aligned on byte boundaries. (The other areas all are aligned on 32-bit word boundaries.)

The Constant Pool

In the heap, each class has a constant pool "attached" to it. Usually created by javac, these constants encode all the names (of variables, methods, and so forth) used by any method in a class. The class contains a count of how many constants there are and an offset that specifies how far into the class description itself the array of constants begins. These constants are typed through specially coded bytes and have a precisely defined format when they appear in the .class file for a class. Later today, a little of this file format is covered, but everything is fully specified by the virtual machine specifications in your Java release.

Limitations

The virtual machine, as currently defined, places some restrictions on legal Java programs by virtue of the choices it has made (some were previously described, and more will be detailed later today).

These limitations and their implications are:

In addition, Sun's implementation of the virtual machine uses so-called _quick bytecodes, which further limit the system. Unsigned 8-bit offsets into objects might limit the number of methods in a class to 256 (this limit might not exist in the final release), and unsigned 8-bit argument counts limit the size of the argument list to 255 32-bit words. (Although this means that you can have up to 255 arguments of most types, you can have only 127 of them if they're all long or double.)

Bytecodes in More Detail

One of the main tasks of the virtual machine is the fast, efficient execution of the Java bytecodes in methods. Unlike in the discussion yesterday about generality versus efficiency, this is a case where speed is of the upmost importance. Every Java program suffers from a slow implementation here, so the runtime must use as many "tricks" as possible to make bytecodes run fast. The only other goal (or limitation) is that Java programmers must not be able to see these tricks in the behavior of their programs.

A Java runtime implementor must be extremely clever to satisfy both of these goals.

The Bytecode Interpreter

A bytecode interpreter examines each opcode byte (bytecode) in a method's bytecode stream, in turn, and executes a unique action for that bytecode. This might consume further bytes for the operands of the bytecode and might affect which bytecode will be examined next. It operates like the hardware CPU in a computer, which examines memory for instructions to carry out in exactly the same manner. It is the software CPU of the Java virtual machine.

Your first, naive attempt to write such a bytecode interpreter will almost certainly be disastrously slow. The inner loop, which dispatches one bytecode each time through the loop, is notoriously difficult to optimize. In fact, smart people have been thinking about this problem, in one form or another, for more than 20 years. Luckily, they've gotten results, all of which can be applied to Java.

The final result is that the interpreter shipped in the current release of Java has an extremely fast inner loop. In fact, on even a relatively slow computer, this interpreter can perform more than 590,000 bytecodes per second! This is really quite good, because the CPU in that computer does only about 30 times better using hardware.

This interpreter is fast enough for most Java programs (and for those requiring more speed, they always can use native methods) but what if a smart implementor wants to do better?

The Just-in-Time Compiler

About a decade ago, a really clever trick was discovered by Peter Deutsch while trying to make Smalltalk run faster. He called it "dynamic translation" during interpretation. Sun calls it "just-in-time" compiling.

The trick is to notice that the really fast interpreter you've just written-in C, for example-already has a useful sequence of native binary code for each bytecode that it interprets: the binary code that the interpreter itself is executing. Because the interpreter already has been compiled from C into native binary code, for each bytecode that it interprets, it passes through a sequence of native code instructions for the hardware CPU on which it is running. By saving a copy of each binary instruction as it "goes by," the interpreter can keep a running log of the binary code it itself has run to interpret a bytecode. It can just as easily keep a log of the set of bytecodes that it ran to interpret an entire method.

You take that log of instructions and "peephole-optimize" it, just as a smart compiler does. This eliminates redundant or unnecessary instructions from the log, and makes it look just like the optimized binary code that a good compiler might have produced.

Note
This is where the name compiler comes from, in "just-in-time" compiler, but it's really only the back end of a traditional compiler-the part that does code generation. By the way, the front end here is javac.

Here's where the trick comes in. The next time that method is run (in exactly the same way), the interpreter now simply can execute directly the stored log of binary native code. Because this optimizes out the inner-loop overhead of each bytecode, as well as any other redundancies between the bytecodes in a method, it can gain a factor of 10 or more in speed. In fact, an experimental version of this technology at Sun has shown that Java programs using it can run as fast as compiled C programs.

Note
The parenthetical in the last paragraph is needed because if anything is different about the input to the method, it takes a different path through the interpreter and must be relogged. (There are sophisticated versions of this technology that solve this, and other, difficulties.) The cache of native code for a method must be invalidated whenever the method has changed, and the interpreter must pay a small cost up front each time a method is run for the first time. However, these small bookkeeping costs are far outweighed by the amazing gains in speed possible.

Currently, Symantec Café version 1.2 has a JIT compiler bundled with it. However, if you still have Symantec 1.0 you can download the Café JIT compiler off the Internet. This JIT compiler that you download not only works for Symantec Café but it also can work with the Java Development Kit. For more information, go to the following site:

http://cafe.symantec.com/jit/index.html

The java2c Translator

Another, simpler, trick, which works well whenever you have a good, portable C compiler on each system that runs your program, is to translate the bytecodes into C and then compile the C into binary native code. If you wait until the first use of a method or class, and then perform this as an "invisible" optimization, it gains you an additional speedup over the approach outlined previously, without the Java programmer needing to know about it.

Of course, this does limit you to systems with a C compiler. In theory, your Java code might be able to travel with its own C compiler, or know where to pull one from the Net as needed, for each new computer and operating system it faced. (Because this violates some of the rules of normal Java code movement over the Net-which is to not have native code on a user's system-it should be used sparingly.)

If you're using Java, for example, to write a server that lives only on your computer, it might be appropriate to use Java for its flexibility in writing and maintaining the server (and for its capability of dynamically linking new Java code on-the-fly), and then to run java2c by hand to translate the basic server itself entirely into native code. You'd link the Java runtime environment into that code so that your server remains a fully capable Java program, but it's now an extremely fast one.

In fact, an experimental version of the java2c translator inside Sun shows that it can reach the speed of compiled and optimized C code. This is the best that you can hope to do!

Note
Unfortunately, as of the 1.0 release, there still is no publicly available java2c tool, and Sun's virtual machine does not perform "just-in-time" compilation. Both of these have been promised in a later release (perhaps 1.1).

The Bytecodes Themselves

Let's look at a progressively less and less detailed description of each class of bytecode.

For each bytecode, some brief text describes its function, and a textual "picture" of the stack, both before and after the bytecode has been executed, is shown. This text picture will look like the following:

..., value1, value2 => ..., value3

This means that the bytecode expects two operands-value1 and value2-to be on the top of the stack, pops them both off the stack, operates on them to produce value3, and pushes value3 back onto the top of the stack. You should read each stack from right to left, with the rightmost value being the top of the stack. The ... is read as "the rest of the stack below," which is irrelevant to the current bytecode. All operands on the stack are 32 bits wide.

Because most bytecodes take their arguments from the stack and place their results back there, the brief text descriptions that follow only say something about the source or destination of values if they are not on the stack. For example, the description "Load integer from local variable." means that the integer is loaded onto the stack, and "Integer add." intends its integers to be taken from-and the result returned to-the stack.

Bytecodes that don't affect control flow simply move the pc onto the next bytecode that follows in sequence. Those that do affect the pc say so explicitly. Whenever you see byte1, byte2, and so forth, it refers to the first byte, second byte, and so on, that follow the opcode byte itself. After such a bytecode is executed, the pc automatically advances over these operand bytes to start the next bytecode in sequence.

Note
The next few sections are in "reference manual style," presenting each bytecode separately in all its (often redundant) detail. Later sections begin to collapse and coalesce this verbose style into something shorter, and more readable. The verbose form is shown at first because the online reference manuals will look more like it, and because it drives home the point that each bytecode "function" comes in many nearly identical bytecodes, one for each primitive type in Java.

Pushing Constants onto the Stack

bipush        ... => ..., value

Push one-byte signed integer. byte1 is interpreted as a signed 8-bit value. This value is expanded to an int and pushed onto the operand stack.

sipush        ... => ..., value

Push two-byte signed integer. byte1 and byte2 are assembled into a signed 16-bit value. This value is expanded to an int and pushed onto the operand stack.

ldc1          ... => ..., item

Push item from constant pool. byte1 is used as an unsigned 8-bit index into the constant pool of the current class. The item at that index is resolved and pushed onto the stack.

ldc2          ... => ..., item

Push item from constant pool. byte1 and byte2 are used to construct an unsigned 16-bit index into the constant pool of the current class. The item at that index is resolved and pushed onto the stack.

ldc2w         ... => ..., constant-word1, constant-word2

Push long or double from constant pool. byte1 and byte2 are used to construct an unsigned 16-bit index into the constant pool of the current class. The two-word constant at that index is resolved and pushed onto the stack.

aconst_null   ... => ..., null

Push the null object reference onto the stack.

iconst_m1     ... => ..., -1

Push the int -1 onto the stack.

iconst_<I>    ... => ..., <I>

Push the int <I> onto the stack. There are six of these bytecodes, one for each of the integers 0-5: iconst_0, iconst_1, iconst_2, iconst_3, iconst_4, and iconst_5.

lconst_<L>    ... => ..., <L>-word1, <L>-word2

Push the long <L> onto the stack. There are two of these bytecodes, one for each of the integers 0 and 1: lconst_0 and lconst_1.

fconst_<F>    ... => ..., <F>

Push the float <F> onto the stack. There are three of these bytecodes, one for each of the integers 0-2: fconst_0, fconst_1, and fconst_2.

dconst_<D>    ... => ..., <D>-word1, <D>-word2

Push the double <D> onto the stack. There are two of these bytecodes, one for each of the integers 0 and 1: dconst_0 and dconst_1.

Loading Local Variables onto the Stack

iload         ... => ..., value

Load int from local variable. Local variable byte1 in the current Java frame must contain an int. The value of that variable is pushed onto the operand stack.

iload_<I>     ... => ..., value

Load int from local variable. Local variable <I> in the current Java frame must contain an int. The value of that variable is pushed onto the operand stack. There are four of these bytecodes, one for each of the integers 0-3: iload_0, iload_1, iload_2, and iload_3.

lload         ... => ..., value-word1, value-word2

Load long from local variable. Local variables byte1 and byte1 + 1 in the current Java frame must together contain a long integer. The values contained in those variables are pushed onto the operand stack.

lload_<L>     ... => ..., value-word1, value-word2

Load long from local variable. Local variables <L> and <L> + 1 in the current Java frame must together contain a long integer. The value contained in those variables is pushed onto the operand stack. There are four of these bytecodes, one for each of the integers 0-3: lload_0, lload_1, lload_2, and lload_3.

fload         ... => ..., value

Load float from local variable. Local variable byte1 in the current Java frame must contain a single precision floating-point number. The value of that variable is pushed onto the operand stack.

fload_<F>     ... => ..., value

Load float from local variable. Local variable <F> in the current Java frame must contain a single precision floating-point number. The value of that variable is pushed onto the operand stack. There are four of these bytecodes, one for each of the integers 0-3: fload_0, fload_1, fload_2, and fload_3.

dload         ... => ..., value-word1, value-word2

Load double from local variable. Local variables byte1 and byte1 + 1 in the current Java frame must together contain a double precision floating-point number. The value contained in those variables is pushed onto the operand stack.

dload_<D>     ... => ..., value-word1, value-word2

Load double from local variable. Local variables <D> and <D> + 1 in the current Java frame must together contain a double precision floating-point number. The value contained in those variables is pushed onto the operand stack There are four of these bytecodes, one for each of the integers 0-3: dload_0, dload1, dload_2, and dload_3.

aload         ... => ..., value

Load object reference from local variable. Local variable byte1 in the current Java frame must contain a return address or reference to an object or array. The value of that variable is pushed onto the operand stack.

aload_<A>     ... => ..., value

Load object reference from local variable. Local variable <A> in the current Java frame must contain a return address or reference to an object. The value of that variable is pushed onto the operand stack. There are four of these bytecodes, one for each of the integers 0-3: aload_0, aload_1, aload_2, and aload_3.

Storing Stack Values into Local Variables

istore        ..., value => ...

Store int into local variable. value must be an int. Local variable byte1 in the current Java frame is set to value.

istore_<I>    ..., value => ...

Store int into local variable. value must be an int. Local variable <I> in the current Java frame is set to value. There are four of these bytecodes, one for each of the integers 0-3: istore_0, istore_1, istore_2, and istore_3.

lstore        ..., value-word1, value-word2 => ...

Store long into local variable. value must be a long integer. Local variables byte1 and byte1 + 1 in the current Java frame are set to value.

lstore_<L>    ..., value-word1, value-word2 => ...

Store long into local variable. value must be a long integer. Local variables <L> and <L> + 1 in the current Java frame are set to value. There are four of these bytecodes, one for each of the integers 0-3: lstore_0, lstore_1, lstore_2, and lstore_3.

fstore        ..., value => ...

Store float into local variable. value must be a single precision floating-point number. Local variable byte1 in the current Java frame is set to value.

fstore_<F>    ..., value => ...

Store float into local variable. value must be a single precision floating-point number. Local variable <F> in the current Java frame is set to value. There are four of these bytecodes, one for each of the integers 0-3: fstore_0, fstore_1, fstore_2, and fstore_3.

dstore        ..., value-word1, value-word2 => ...

Store double into local variable. value must be a double precision floating-point number. Local variables byte1 and byte1 + 1 in the current Java frame are set to value.

dstore_<D>    ..., value-word1, value-word2 => ...

Store double into local variable. value must be a double precision floating-point number. Local variables <D> and <D> + 1 in the current Java frame are set to value. There are four of these bytecodes, one for each of the integers 0-3: dstore_0, dstore_1, dstore_2, and dstore_3.

astore        ..., handle => ...

Store object reference into local variable. handle must be a return address or a reference to an object. Local variable byte1 in the current Java frame is set to value.

astore_<A>    ..., handle => ...

Store object reference into local variable. handle must be a return address or a reference to an object. Local variable <A> in the current Java frame is set to value. There are four of these bytecodes, one for each of the integers 0-3: astore_0, astore_1, astore_2, and astore_3.

iinc          -no change-

Increment local variable by constant. Local variable byte1 in the current Java frame must contain an int. Its value is incremented by the value byte2, where byte2 is treated as a signed 8-bit quantity.

Managing Arrays

newarray        ..., size => result

Allocate new array. size must be an int. It represents the number of elements in the new array. byte1 is an internal code that indicates the type of array to allocate. Possible values for byte1 are as follows: T_BOOLEAN (4), T_chAR (5), T_FLOAT (6), T_DOUBLE (7), T_BYTE (8), T_SHORT (9), T_INT (10), and T_LONG (11).

An attempt is made to allocate a new array of the indicated type, capable of holding size elements. This will be the result. If size is less than zero, a NegativeArraySizeException is thrown. If there is not enough memory to allocate the array, an OutOfMemoryError is thrown. All elements of the array are initialized to their default values.

anewarray       ..., size => result

Allocate a new array of objects. size must be an int. It represents the number of elements in the new array. byte1 and byte2 are used to construct an index into the constant pool of the current class. The item at that index is resolved. The resulting entry must be a class.

An attempt is made to allocate a new array of the indicated class type, capable of holding size elements. This will be the result. If size is less than zero, a NegativeArraySizeException is thrown. If there is not enough memory to allocate the array, an OutOfMemoryError is thrown. All elements of the array are initialized to null.

Note
anewarray is used to create a single dimension of an array of objects. For example, the request new Thread[7] generates the following bytecodes:
    bipush 7
    anewarray <Class "java.lang.Thread">
anewarray also can be used to create the outermost dimension of a multidimensional array. For example, the array declaration new int[6][] generates this:
    bipush 6
    anewarray <Class "[I">
(See the section, "Method Signatures," for more information on strings such as [I.)

multianewarray  ..., size1 size2...sizeN => result

Allocate a new multidimensional array. Each size<I> must be an int. Each represents the number of elements in a dimension of the array. byte1 and byte2 are used to construct an index into the constant pool of the current class. The item at that index is resolved. The resulting entry must be an array class of one or more dimensions.

byte3 is a positive integer representing the number of dimensions being created. It must be less than or equal to the number of dimensions of the array class. byte3 is also the number of elements that are popped off the stack. All must be ints greater than or equal to zero. These are used as the sizes of the dimensions. An attempt is made to allocate a new array of the indicated class type, capable of holding size<1> * size<2> * ... * <sizeN> elements. This will be the result. If any of the size<I> arguments on the stack is less than zero, a NegativeArraySizeException is thrown. If there is not enough memory to allocate the array, an OutOfMemoryError is thrown.

Note
new int[6][3][] generates these bytecodes:
    bipush 6
    bipush 3
    multianewarray <Class "[[[I"> 2
It's more efficient to use newarray or anewarray when creating arrays of single dimension.

arraylength     ..., array => ..., length

Get length of array. array must be a reference to an array object. The length of the array is determined and replaces array on the top of the stack. If array is null, a NullPointerException is thrown.

iaload          ..., array, index => ..., value
laload          ..., array, index => ..., value-word1, value-word2
faload          ..., array, index => ..., value
daload          ..., array, index => ..., value-word1, value-word2
aaload          ..., array, index => ..., value
baload          ..., array, index => ..., value
caload          ..., array, index => ..., value
saload          ..., array, index => ..., value

Load <type> from array. array must be an array of <type>s. index must be an int. The <type> value at position number index in array is retrieved and pushed onto the top of the stack. If array is null, a NullPointerException is thrown. If index is not within the bounds of array, an ArrayIndexOutOfBoundsException is thrown. <type> is, in turn, int, long, float, double, object reference, byte, char, and short. <type>s long and double have two word values, as you've seen in previous load bytecodes.

iastore         .., array, index, value => ...
lastore         ..., array, index, value-word1, value-word2 => ...
fastore         ..., array, index, value => ...
dastore         ..., array, index, value-word1, value-word2 => ...
aastore         ..., array, index, value => ...
bastore         ..., array, index, value => ...
castore         ..., array, index, value => ...
sastore         ..., array, index, value => ...

Store into <type> array. array must be an array of <type>s, index must be an int, and value a <type>. The <type> value is stored at position index in array. If array is null, a NullPointerException is thrown. If index is not within the bounds of array, an ArrayIndexOutOfBoundsException is thrown. <type> is, in turn, int, long, float, double, object reference, byte, char, and short. <type>s long and double have two word values, as you've seen in previous store bytecodes.

Stack Operations

nop        -no change-

Do nothing.

pop        ..., any => ...

Pop the top word from the stack.

pop2       ..., any2, any1 => ...

Pop the top two words from the stack.

dup        ..., any => ..., any, any

Duplicate the top word on the stack.

dup2       ..., any2, any1 => ..., any2, any1, any2,any1

Duplicate the top two words on the stack.

dup_x1     ..., any2, any1 => ..., any1, any2,any1

Duplicate the top word on the stack and insert the copy two words down in the stack.

dup2_x1    ..., any3, any2, any1 => ..., any2, any1, any3,any2,any1

Duplicate the top two words on the stack and insert the copies two words down in the stack.

dup_x2     ..., any3, any2, any1 => ..., any1, any3,any2,any1

Duplicate the top word on the stack and insert the copy three words down in the stack.

dup2_x2    ..., any4, any3, any2, any1 => ..., any2, any1, any4,any3,any2,any1

Duplicate the top two words on the stack and insert the copies three words down in the stack.

swap       ..., any2, any1 => ..., any1, any2

Swap the top two elements on the stack.

Arithmetic Operations

iadd       ..., v1, v2 => ..., result
ladd       ..., v1-word1, v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2
fadd       ..., v1, v2 => ..., result
dadd       ..., v1-word1, v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2

v1 and v2 must be <type>s. The vs are added and are replaced on the stack by their <type> sum. <type> is, in turn, int, long, float, and double.

isub       ..., v1, v2 => ..., result
lsub       ..., v1-word1, v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2
fsub       ..., v1, v2 => ..., result
dsub       ..., v1-word1, v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2

v1 and v2 must be <type>s. v2 is subtracted from v1, and both vs are replaced on the stack by their <type> difference. <type> is, in turn, int, long, float, and double.

imul       ..., v1, v2 => ..., result
lmul       ..., v1-word1, v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2
fmul       ..., v1, v2 => ..., result
dmul       ..., v1-word1, v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2

v1 and v2 must be <type>s. Both vs are replaced on the stack by their <type> product. <type> is, in turn, int, long, float, and double.

idiv       ..., v1, v2 => ..., result
ldiv       ..., v1-word1, v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2
fdiv       ..., v1, v2 => ..., result
ddiv       ..., v1-word1, v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2

v1 and v2 must be <type>s. v2 is divided by v1, and both vs are replaced on the stack by their <type> quotient. An attempt to divide by zero results in an ArithmeticException being thrown. <type> is, in turn, int, long, float, and double.

irem       ..., v1, v2 => ..., result
lrem       ..., v1-word1, v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2
frem       ..., v1, v2 => ..., result
drem       ..., v1-word1, v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2

v1 and v2 must be <type>s. v2 is divided by v1, and both vs are replaced on the stack by their <type> remainder. An attempt to divide by zero results in an ArithmeticException being thrown. <type> is, in turn, int, long, float, and double.

ineg       ..., value => ..., result
lneg       ..., value-word1, value-word2 => ..., result-word1, result-word2
fneg       ..., value => ..., result
dneg       ..., value-word1, value-word2 => ..., result-word1, result-word2

value must be a <type>. It is replaced on the stack by its arithmetic negation. <type> is, in turn, int, long, float, and double.

Note
Now that you're familiar with the look of the bytecodes, the summaries that follow will become shorter and shorter (for space reasons). You always can get any desired level of detail from the full virtual machine specification in the latest Java release.

Logical Operations

ishl       ..., v1, v2 => ..., result
lshl       ..., v1-word1, v1-word2, v2 => ..., r-word1, r-word2
ishr       ..., v1, v2 => ..., result
lshr       ..., v1-word1, v1-word2, v2 => ..., r-word1, r-word2
iushr      ..., v1, v2 => ..., result
lushr      ..., v1-word1, v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2

For types int and long: arithmetic shift-left, shift-right, and logical shift-right.

iand       ..., v1, v2 => ..., result
land       ..., v1-word1, v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2
ior        ..., v1, v2 => ..., result
lor        ..., v1-word1, v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2
ixor       ..., v1, v2 => ..., result
lxor       ..., v1-word1, v1-word2, v2-word1, v2-word2 => ..., r-word1, r-word2

For types int and long: bitwise AND, OR, and XOR.

Conversion Operations

i2l         ..., value => ..., result-word1, result-word2
i2f         ..., value => ..., result
i2d         ..., value => ..., result-word1, result-word2
l2i         ..., value-word1, value-word2 => ..., result
l2f         ..., value-word1, value-word2 => ..., result
l2d         ..., value-word1, value-word2 => ..., result-word1, result-word2
f2i         ..., value => ..., result
f2l         ..., value => ..., result-word1, result-word2
f2d         ..., value => ..., result-word1, result-word2
d2i         ..., value-word1, value-word2 => ..., result
d2l         ..., value-word1, value-word2 => ..., result-word1, result-word2
d2f         ..., value-word1, value-word2 => ..., result

int2byte    ..., value => ..., result
int2char    ..., value => ..., result
int2short   ..., value => ..., result

These bytecodes convert from a value of type <lhs> to a result of type <rhs>. <lhs> and <rhs> can be any of i, l, f,and d, which represent int, long, float, and double, respectively. The final three bytecodes convert types that are self-explanatory.

Transfer of Control

ifeq        ..., value => ...
ifne        ..., value => ...
iflt        ..., value => ...
ifgt        ..., value => ...
ifle        ..., value => ...
ifge        ..., value => ...

if_icmpeq   ..., value1, value2 => ...
if_icmpne   ..., value1, value2 => ...
if_icmplt   ..., value1, value2 => ...
if_icmpgt   ..., value1, value2 => ...
if_icmple   ..., value1, value2 => ...
if_icmpge   ..., value1, value2 => ...
ifnull      ..., value => ...
ifnonnull   ..., value => ...

When value <rel> 0 is true in the first set of bytecodes, value1 <rel> value2 is true in the second set, or value is null (or not null) in the third, byte1 and byte2 are used to construct a signed 16-bit offset. Execution proceeds at that offset from the pc. Otherwise, execution proceeds at the following bytecode. <rel> is one of eq, ne, lt, gt, le, and ge, which represent equal, not equal, less than, greater than, less than or equal, and greater than or equal, respectively.

lcmp        ..., v1-word1, v1-word2, v2-word1, v2-word2 => ..., result

fcmpl       ..., v1, v2 => ..., result
dcmpl       ..., v1-word1, v1-word2, v2-word1, v2-word2 => ..., result

fcmpg       ..., v1, v2 => ..., result
dcmpg       ..., v1-word1, v1-word2, v2-word1, v2-word2 => ..., result

v1 and v2 must be long, float, or double. They are both popped from the stack and compared. If v1 is greater than v2, the int value 1 is pushed onto the stack. If v1 is equal to v2, 0 is pushed onto the stack. If v1 is less than v2, -1 is pushed onto the stack. For floating-point, if either v1 or v2 is NaN, -1 is pushed onto the stack for the first pair of bytecodes, and +1 for the second pair.

if_acmpeq   ..., value1, value2 => ...
if_acmpne   ..., value1, value2 => ...

Branch if object references are equal/not equal. value1 and value2 must be references to objects. They are both popped from the stack. If value1 is equal/not equal to value2, byte1 and byte2 are used to construct a signed 16-bit offset. Execution proceeds at that offset from the pc. Otherwise, execution proceeds at the bytecode following.

goto        -no change-
goto_w      -no change-

Branch always. byte1 and byte2 (plus byte3 and byte4 for goto_w) are used to construct a signed 16-bit (32-bit) offset. Execution proceeds at that offset from the pc.

jsr         ... => ..., return-address
jsr-w       ... => ..., return-address

Jump subroutine. The address of the bytecode immediately following the jsr is pushed onto the stack. byte1 and byte2 (plus byte3 and byte4 for goto_w) are used to construct a signed 16-bit (32-bit) offset. Execution proceeds at that offset from the pc.

ret         -no change-
ret2_w      -no change-

Return from subroutine. Local variable byte1 (plus byte2 for ret_w are assembled into a 16-bit index) in the current Java frame must contain a return address. The contents of that local variable are written into the pc.

Note
jsr pushes the address onto the stack, and ret gets it out of a local variable. This asymmetry is intentional. The jsr and ret bytecodes are used in the implementation of Java's finally keyword.

Method Return

return      ... => [empty]

Return (void) from method. All values on the operand stack are discarded. The interpreter then returns control to its caller.

ireturn     ..., value => [empty]
lreturn     ..., value-word1, value-word2 => [empty]
freturn     ..., value => [empty]
dreturn     ..., value-word1, value-word2 => [empty]
areturn     ..., value => [empty]

Return <type> from method. value must be a <type>. The value is pushed onto the stack of the previous execution environment. Any other values on the operand stack are discarded. The interpreter then returns control to its caller. <type> is, in turn, int, long, float, double, and object reference.

Note
The stack behavior of the "return" bytecodes might be confusing to anyone expecting the Java operand stack to be just like the C stack. Java's operand stack actually consists of a number of discontiguous segments, each corresponding to a method call. A return bytecode empties the Java operand stack segment corresponding to the frame of the returning call, but does not affect the segment of any parent calls.

Table Jumping

tableswitch   ..., index => ...

tableswitch is a variable-length bytecode. Immediately after the tableswitch opcode, zero to three 0 bytes are inserted as padding so that the next byte begins at an address that is a multiple of four. After the padding are a series of signed 4-byte quantities: default-offset, low, high, and then (high - low + 1) further signed 4-byte offsets. These offsets are treated as a zero- based jump table.

The index must be an int. If index is less than low or index is greater than high, default-offset is added to the pc. Otherwise, the (index - low)'th element of the jump table is extracted and added to the pc.

lookupswitch  ..., key => ...

lookupswitch is a variable-length bytecode. Immediately after the lookupswitch opcode, zero to three 0 bytes are inserted as padding so that the next byte begins at an address that is a multiple of four. Immediately after the padding is a series of pairs of signed 4-byte quantities. The first pair is special; it contains the default-offset and the number of pairs that follow. Each subsequent pair consists of a match and an offset.

The key on the stack must be an int. This key is compared to each of the matches. If it is equal to one of them, the corresponding offset is added to the pc. If the key does not match any of the matches, the default-offset is added to the pc.

Manipulating Object Fields

putfield      ..., handle, value => ...
putfield      ..., handle, value-word1, value-word2 => ...

Set field in object. byte1 and byte2 are used to construct an index into the constant pool of the current class. The constant pool item is a field reference to a class name and a field name. The item is resolved to a field block pointer containing the field's width and offset (both in bytes).

The field at that offset from the start of the instance pointed to by handle will be set to the value on the top of the stack. The first stack picture is for 32-bit, and the second for 64-bit wide fields. This bytecode handles both. If handle is null, a NullPointerException is thrown. If the specified field is a static field, an IncompatibleClassChangeError is thrown.

getfield      ..., handle => ..., value
getfield      ..., handle => ..., value-word1, value-word2

Fetch field from object. byte1 and byte2 are used to construct an index into the constant pool of the current class. The constant pool item will be a field reference to a class name and a field name. The item is resolved to a field block pointer containing the field's width and offset (both in bytes).

handle must be a reference to an object. The value at offset into the object referenced by handle replaces handle on the top of the stack. The first stack picture is for 32-bit, and the second for 64-bit wide fields. This bytecode handles both. If the specified field is a static field, an IncompatibleClassChangeError is thrown.

putstatic     ..., value => ...
putstatic     ..., value-word1, value-word2 => ...

Set static field in class. byte1 and byte2 are used to construct an index into the constant pool of the current class. The constant pool item will be a field reference to a static field of a class. That field will be set to have the value on the top of the stack. The first stack picture is for 32-bit, and the second for 64-bit wide fields. This bytecode handles both. If the specified field is not a static field, an IncompatibleClassChangeError is thrown.

getstatic     ..., => ..., value_
getstatic     ..., => ..., value-word1, value-word2

Get static field from class. byte1 and byte2 are used to construct an index into the constant pool of the current class. The constant pool item will be a field reference to a static field of a class. The value of that field is placed on the top of the stack. The first stack picture is for 32-bit, and the second for 64-bit wide fields. This bytecode handles both. If the specified field is not a static field, an IncompatibleClassChangeError is thrown.

Method Invocation

invokevirtual     ..., handle, [arg1, [arg2, ...]], ... => ...

Invoke instance method based on runtime type. The operand stack must contain a reference to an object and some number of arguments. byte1 and byte2 are used to construct an index into the constant pool of the current class. The item at that index in the constant pool contains the complete method signature. A pointer to the object's method table is retrieved from the object reference. The method signature is looked up in the method table. The method signature is guaranteed to match exactly one of the method signatures in the table.

The result of the lookup is an index into the method table of the named class that's used to look in the method table of the object's runtime type, where a pointer to the method block for the matched method is found. The method block indicates the type of method (native, synchronized, and so on) and the number of arguments (nargs) expected on the operand stack.

If the method is marked synchronized, the monitor associated with handle is entered.

The base of the local variables array for the new Java stack frame is set to point to handle on the stack, making handle and the supplied arguments (arg1, arg2, ...) the first nargs local variables of the new frame. The total number of local variables used by the method is determined, and the execution environment of the new frame is pushed after leaving sufficient room for the locals. The base of the operand stack for this method invocation is set to the first word after the execution environment. Finally, execution continues with the first bytecode of the matched method.

If handle is null, a NullPointerException is thrown. If during the method invocation a stack overflow is detected, a StackOverflowError is thrown.

invokenonvirtual  ..., handle, [arg1, [arg2, ...]], ... => ...

Invoke instance method based on compile-time type. The operand stack must contain a reference (handle) to an object and some number of arguments. byte1 and byte2 are used to construct an index into the constant pool of the current class. The item at that index in the constant pool contains the complete method signature and class. The method signature is looked up in the method table of the class indicated. The method signature is guaranteed to match exactly one of the method signatures in the table.

The result of the lookup is a method block. The method block indicates the type of method (native, synchronized, and so on) and the number of arguments (nargs) expected on the operand stack. (The last three paragraphs are identical to the previous bytecode.)

invokestatic      ..., , [arg1, [arg2, ...]], ... => ...

Invoke class (static) method. The operand stack must contain some number of arguments. byte1 and byte2 are used to construct an index into the constant pool of the current class. The item at that index in the constant pool contains the complete method signature and class. The method signature is looked up in the method table of the class indicated. The method signature is guaranteed to match one of the method signatures in the class's method table exactly.

The result of the lookup is a method block. The method block indicates the type of method (native, synchronized, and so on) and the number of arguments (nargs) expected on the operand stack.

If the method is marked synchronized, the monitor associated with the class is entered. (The last two paragraphs are identical to those in invokevirtual, except that no NullPointerException can be thrown.)

invokeinterface   ..., handle, [arg1, [arg2, ...]], ...=> ...

Invoke interface method. The operand stack must contain a reference (handle) to an object and some number of arguments. byte1 and byte2 are used to construct an index into the constant pool of the current class. The item at that index in the constant pool contains the complete method signature. A pointer to the object's method table is retrieved from the object reference. The method signature is looked up in the method table. The method signature is guaranteed to match exactly one of the method signatures in the table.

The result of the lookup is a method block. The method block indicates the type of method (native, synchronized, and so on), but, unlike the other "invoke" bytecodes, the number of available arguments (nargs) is taken from byte3; byte4 is reserved for future use. (The last three paragraphs are identical to those in invokevirtual.)

Exception Handling

athrow            ..., handle => [undefined]

Throw exception. handle must be a handle to an exception object. That exception, which must be an instance of Throwable (or a subclass), is thrown. The current Java stack frame is searched for the most recent catch clause that handles the exception. If a matching "catch-list" entry is found, the pc is reset to the address indicated by the catch-list pointer, and execution continues there.

If no appropriate catch clause is found in the current stack frame, that frame is popped and the exception is rethrown, starting the process all over again in the parent frame. If handle is null, then a NullPointerException is thrown instead.

Miscellaneous Object Operations

new               ... => ..., handle

Create new object. byte1 and byte2 are used to construct an index into the constant pool of the current class. The item at that index should be a class name that can be resolved to a class pointer, class. A new instance of that class then is created and a reference (handle) for the instance is placed on the top of the stack.

checkcast         ..., handle => ..., [handle | ...]

Make sure object is of given type. handle must be a reference to an object. byte1 and byte2 are used to construct an index into the constant pool of the current class. The string at that index of the constant pool is presumed to be a class name that can be resolved to a class pointer, class.

checkcast determines whether handle can be cast to a reference to an object of that class. (A null handle can be cast to any class.) If handle can legally be cast, execution proceeds at the next bytecode, and the handle remains on the stack. If not, a ClassCastException is thrown and the stack is emptied.

instanceof        ..., handle => ..., result

Determine whether object is of given type. handle must be a reference to an object. byte1 and byte2 are used to construct an index into the constant pool of the current class. The string at that index of the constant pool is presumed to be a class name that can be resolved to a class pointer, class.

If handle is null, the result is 0 (false). Otherwise, instanceof determines whether handle can be cast to a reference to an object of that class. The result is 1 (true) if it can, and 0 (false) otherwise.

Monitors

monitorenter      ..., handle => ...

Enter monitored region of code. handle must be a reference to an object. The interpreter attempts to obtain exclusive access through a lock mechanism to handle. If another thread already has handle locked, the current thread waits until the handle is unlocked. If the current thread already has handle locked, execution continues normally. If handle has no lock on it, this bytecode obtains an exclusive lock. (A null in either bytecode throws NullPointerException.)

monitorexit       ..., handle => ...

Exit monitored region of code. handle must be a reference to an object. The lock on handle is released. If this is the last lock that this thread has on that handle (one thread is allowed to have multiple locks on a single handle), other threads that are waiting for handle are allowed to proceed. (A null in either bytecode throws NullPointerException.)

Debugging

breakpoint        -no change-

Call breakpoint handler. The breakpoint bytecode is used to overwrite a bytecode to force control temporarily back to the debugger prior to the effect of the overwritten bytecode. The original bytecode's operands (if any) are not overwritten, and the original bytecode is restored when the breakpoint bytecode is removed.

The _quick Bytecodes

The following discussion, straight out of the Java virtual machine documentation, shows you an example of the cleverness mentioned earlier that's needed to make a bytecode interpreter fast:

The following set of pseudo-bytecodes, suffixed by _quick, are all variants of standard Java bytecodes. They are used by the runtime to improve the execution speed of the bytecode interpreter. They aren't officially part of the virtual machine specification and are invisible outside a Java virtual machine implementation. However, inside that implementation they have proven to be an effective optimization.
First, you should know that javac still generates only non-_quick bytecodes. Second, all bytecodes that have a _quick variant reference the constant pool. When _quick optimization is turned on, each non-_quick bytecode (that has a _quick variant) resolves the specified item in the constant pool, signals an error if the item in the constant pool could not be resolved for some reason, turns itself into the _quick variant of itself, and then performs its intended operation.
This is identical to the actions of the non-_quick bytecode, except for the step of overwriting itself with its _quick variant. The _quick variant of a bytecode assumes that the item in the constant pool has already been resolved, and that this resolution did not produce any errors. It simply performs the intended operation on the resolved item.

Thus, as your bytecodes are being interpreted, they are automatically getting faster and faster! Here are all the _quick variants in the current Java runtime:

ldc1_quick
ldc2_quick
ldc2w_quick

anewarray_quick
multinewarray_quick

putfield_quick
putfield2_quick
getfield_quick
getfield2_quick
putstatic_quick
putstatic2_quick
getstatic_quick
getstatic2_quick

invokevirtual_quick
invokevirtualobject_quick
invokenonvirtual_quick
invokestatic_quick
invokeinterface_quick

new_quick
checkcast_quick
instanceof_quick

If you would like to go back in today's lesson and look at what each of these does, you can find the name of the original bytecode on which a _quick variant is based by simply removing the _quick from its name. The bytecodes putstatic, getstatic, putfield, and getfield have two _quick variants each, one for each stack picture in their original descriptions. invokevirtual has two variants: one for objects and one for arrays (to do fast lookups in java.lang.Object).

Note
One last note on the _quick optimization, regarding the unusual handling of the constant pool (for detail fanatics only):
When a class is read in, an array constant_pool[] of size nconstants is created and assigned to a field in the class. constant_pool[0] is set to point to a dynamically allocated array that indicates which fields in the constant_pool already have been resolved. constant_pool[1] through constant_pool[nconstants - 1] are set to point at the "type" field that corresponds to this constant item.
When a bytecode is executed that references the constant pool, an index is generated, and constant_pool[0] is checked to see whether the index already has been resolved. If so, the value of constant_pool[index] is returned. If not, the value of constant_pool[index] is resolved to be the actual pointer or data, and overwrites whatever value was already in constant_pool[index].

The .class File Format

You won't be given the entire .class file format here, only a taste of what it's like. (You can read all about it in the release documentation.) It's mentioned here because it is one of the parts of Java that needs to be specified carefully if all Java implementations are to be compatible with one another, and if Java byte codes are expected to travel across arbitrary networks-to and from arbitrary computers and operating systems-and yet arrive safely.

The rest of this section paraphrases, and extensively condenses, the latest release of the .class documentation.

.class files are used to hold the compiled versions of both Java classes and Java interfaces. Compliant Java interpreters must be capable of dealing with all .class files that conform to the following specification.

A Java .class file consists of a stream of 8-bit bytes. All 16-bit and 32-bit quantities are constructed by reading in two or four 8-bit bytes, respectively. The bytes are joined together in big-endian order. (Use java.io.DataInput and java.io.DataOutput to read and write class files.)

The class file format is presented below as a series of C-struct-like structures. However, unlike a C struct, there is no padding or alignment between pieces of the structure, each field of the structure may be of variable size, and an array may be of variable size (in this case, some field prior to the array gives the array's dimension). The types u1, u2, and u4 represent an unsigned one-, two-, or four-byte quantity, respectively.

Attributes are used at several different places in the .class format. All attributes have the following format:

GenericAttribute_info {
    u2 attribute_name;
    u4 attribute_length;
    u1 info[attribute_length];
}

The attribute_name is a 16-bit index into the class's constant pool; the value of constant_pool[attribute_name] is a string giving the name of the attribute. The field attribute_length gives the length of the subsequent information in bytes. This length does not include the four bytes needed to store attribute_name and attribute_length. In the following text, whenever an attribute is required, names of all the attributes that are currently understood are listed. In the future, more attributes will be added. Class file readers are expected to skip over and ignore the information in any attributes that they do not understand.

The following pseudo-structure gives a top-level description of the format of a class file:

ClassFile {
    u4  magic;
    u2  minor_version;
    u2  major_version;
    u2  constant_pool_count;
    cp_info         constant_pool[constant_pool_count - 1];
    u2  access_flags;
    u2  this_class;
    u2  super_class;
    u2  interfaces_count;
    u2  interfaces[interfaces_count];
    u2  fields_count;
    field_info      fields[fields_count];
    u2  methods_count;
    method_info     methods[methods_count];
    u2  attributes_count;
    attribute_info  attributes[attribute_count];
}

Here's one of the smaller structures used:

method_info {
    u2  access_flags;
    u2  name_index;
    u2  signature_index;
    u2  attributes_count;
    attribute_info  attributes[attribute_count];
}

Finally, here's a sample of one of the later structures in the .class file description:

Code_attribute {
    u2  attribute_name_index;
    u2  attribute_length;
    u1  max_stack;
    u1  max_locals;
    u2  code_length;
    u1  code[code_length];
    u2  exception_table_length;
    {  u2   start_pc;
       u2   end_pc;
       u2   handler_pc;
       u2   catch_type;
    }  exception_table[exception_table_length];
    u2  attributes_count;
    attribute_info  attributes[attribute_count];
}

None of this is meant to be completely comprehensible (though you might be able to guess at what a lot of the structure members are for), but just suggestive of the sort of structures that live inside .class files. Because the compiler and runtime sources are available, you always can begin with them if you actually have to read or write .class files yourself. Thus, you don't need to have a deep understanding of the details, even in that case.

Method Signatures

Because method signatures are used in .class files, now is an appropriate time to explore them in the detail promised on earlier days-but they're probably most useful to you when writing the native methods.

A signature is a string representing the type of a method, field, or array.

A field signature represents the value of an argument to a method or the value of a variable and is a series of bytes in the following grammar:

    <field signature> := <field_type>
    <field type>      := <base_type> | <object_type> | <array_type>
    <base_type>       := B | C | D | F | I | J | S | Z
    <object_type>     := L <full.ClassName> ;
    <array_type>      := [ <optional_size> <field_type>
    <optional_size>   := [0-9]*

Here are the meanings of the base types: B (byte), C (char), D (double), F (float), I (int), J (long), S (short), and Z (boolean).

A return-type signature represents the return value from a method and is a series of bytes in the following grammar:

    <return signature>    := <field type> | V

The character V (void) indicates that the method returns no value. Otherwise, the signature indicates the type of the return value. An argument signature represents an argument passed to a method:

    <argument signature>  := <field type>

Finally, a method signature represents the arguments that the method expects, and the value that it returns:

    <method_signature>    := (<arguments signature>) <return signature>
    <arguments signature> := <argument signature>*

Let's try out the new rules: A method called complexMethod() in the class my.package.name.ComplexClass takes three arguments-a long, a boolean, and a two-dimensional array of shorts-and returns this. Then, (JZ[[S)Lmy.package.name.ComplexClass; is its method signature.

A method signature often is prefixed by the name of the method, or by its full package (using an underscore in the place of dots) and its class name followed by a slash / and the name of the method, to form a complete method signature. Now, at last, you have the full story! Thus, the following:

my_package_name_ComplexClass/complexMethod(JZ[[S)Lmy.package.name.ComplexClass;

is the full, complete method signature of complexMethod(). (Phew!)

The Garbage Collector

Decades ago, programmers in both the Lisp and the Smalltalk communities realized how extremely valuable it is to be able to ignore memory deallocation. They realized that although allocation is fundamental, deallocation is forced on the programmer by the laziness of the system-it should be able to figure out what is no longer useful, and get rid of it. In relative obscurity, these pioneering programmers developed a whole series of garbage collectors to perform this job, each getting more sophisticated and efficient as the years went by. Finally, now that the mainstream programming community has begun to recognize the value of this automated technique, Java can become the first really widespread application of the technology those pioneers developed.

The Problem

Imagine that you're a programmer in a C-like language (probably not too difficult for you, because these languages are the dominant ones right now). Each time you create something-anything-dynamically in such a language, you completely are responsible for tracking the life of this object throughout your program and mentally deciding when it will be safe to deallocate it. This can be quite a difficult (sometimes impossible) task, because any of the other libraries or methods you've called might have "squirreled away" a pointer to the object, unbeknownst to you. When it becomes impossible to know, you simply choose never to deallocate the object, or at least to wait until every library and method call involved has completed, which could be nearly as long.

The uneasy feeling you get when writing such code is a natural, healthy response to what is inherently an unsafe and unreliable style of programming. If you have tremendous discipline-and so does everyone who writes every library and method you call-you can, in principle, survive this responsibility without too many mishaps. But aren't you human? Aren't they? There must be some small slips in this perfect discipline due to error. What's worse, such errors are virtually undetectable, as anyone who's tried to hunt down a stray pointer problem in C will tell you. What about the thousands of programmers who don't have that sort of discipline?

Another way to ask this question is: Why should any programmers be forced to have this discipline, when it is entirely possible for the system to remove this heavy burden from their shoulders?

Software engineering estimates have recently shown that for every 55 lines of production
C-like code in the world, there is one bug. This means that your electric razor has about 80 bugs, and your TV, 400. Soon they will have even more, because the size of this kind of embedded computer software is growing exponentially. When you begin to think of how much C-like code is in your car's engine, it should give you pause.

Many of these errors are due to the misuse of pointers, by misunderstanding or by accident, and to the early, incorrect freeing of allocated objects in memory. Java addresses both of these-the former by eliminating explicit pointers from the Java language altogether, and the latter by including, in every Java system, a garbage collector that solves the problem.

The Solution

Imagine a runtime system that tracks each object you create, notices when the last reference to it has vanished, and frees the object for you. How could such a thing actually work?

One brute-force approach, tried early in the days of garbage collecting, is to attach a reference counter to every object. When the object is created, the counter is set to 1. Each time a new reference to the object is made, the counter is incremented, and each time such a reference disappears, the counter is decremented. Because all such references are controlled by the language-as variables and assignments, for example-the compiler can tell whenever an object reference might be created or destroyed, just as it does in handling the scoping of local variables, and thus it can assist with this task. The system itself "holds onto" a set of root objects that are considered too important to be freed. The class Object is one example of such a V.I.P. object. (V.I.O.?) Finally, all that's needed is to test, after each decrement, whether the counter has hit 0. If it has, the object is freed.

If you think carefully about this approach, you soon will convince yourself that it definitely is correct when it decides to free anything. It is so simple that you can immediately tell that it will work. The low-level hacker in you also might feel that if it's that simple, it's probably not fast enough to run at the lowest level of the system-and you'd be right.

Think about all of the stack frames, local vari