from the host computer's libraries or from another object downloaded off the Net.
This situation differs from languages like C++, where a compiler scans the source code and resolves the correct method out of several. Then it embeds a pointer to this method within the generated machine code. This
static linking
works well for self-contained programs that run locally on only one machine. However, moving a program to another system requires copying all of the program, not just a portion of it. Also, if a new class inherits properties from an existing class, then the sour
ce code must be recompiled so that the linker can reevaluate all the method calls. Because Java's dynamic linking mechanism resolves the correct method at run time, it enables preexisting code modules to migrate to a new machine and link up quickly with the local code libraries.
When a Java program is "compiled," it is converted into bytecodes for interpretation on a hypothetical virtual machine (VM). Each computer runs its own VM implementation locally, and this is where the dynamic linking happens. Four different bytecodes, as described by the Java VM specification, handle executing a method. Each bytecode applies to a different method type. The most common bytecode is known by the label
INVOKE_VIRTUAL
, and it handles most method calls. The call is dispatched based on the run-time (or virtual) type of the object that owns the method.
Dynamic Links
How does Java establish the run-time links with programs that have just come from the Internet? Objects in these pro
grams get loaded into a pool of constants located in memory. This pool, called a
class constant pool
, stores data constants, program bytecodes, class descriptions, and method descriptions. The text string of each method's name, combined with extra information, is encoded and stored in a
method signature table
in the program's constant pool.
When the Java VM executes a program and encounters an
INVOKE_VIRTUAL
bytecode, it pushes the method's parameters and a pointer to the object onto the stack. A 2-byte reference number follows the
INVOKE_VIRTUAL
bytecode, which the Java VM uses as an index into the calling object's constant pool to obtain the method's signature. The VM uses the pointer on the stack to locate the target object's method table. It searches this table until it finds a method block with the proper signature. This
method block
contains information as to the method's type, how its call is set up, and where its code is located in memory (as shown in
the figure
"Java's Hot Links"
). With the start address of the appropriate method in hand, the Java virtual machine begins executing it and retrieves the arguments off the stack.
The amount of indirection here is a product of compromise. The first indexed lookup into the constant pool is necessary in order to limit the size of the compiled code. Only 2 bytes are needed to specify the correct method, while searches based on a name string require both more storage as well as more cycles. The result is more compact program code, particularly if some methods are called frequently.
The second lookup is more complicated because the VM must locate the appropriate method for the chosen object. Unfortunately, the object-oriented nature of Java means the compiler can't predict a method's type when it runs. So, the VM uses the signature mechanism to find the correct method in the class to execute.
The Java VM does include a neat trick to optimize code at run time. If you call the
function
foo()
repeatedly from inside the same method, only the first call goes through the elaborate lookup process. Subsequent calls become much faster because the VM substitutes
INVOKE_VIRTUAL_QUICK
bytecodes in place of the
INVOKE_VIRTUAL
bytecodes. The index bytes after this
QUICK
version are already decoded references to the method. The Java VM specification suggests that this optimization occur automatically on-the-fly, but different Java implementations might approach this differently.
Speed Tricks
It should be clear that the dynamic links established for methods in a Java program are much more involved than for a statically linked language like C++. The overhead for switching the processor state between functions is still one of the biggest problems for compiler writers to tackle, and the standard solution to reduce this overhead is
method inlining
. That is, a method's instructions are simply copied and pasted into the calling meth
od so they replace the method call. All the overhead in making the method call -- the pushing and popping of the stack, and the table searches used by the dynamic linking mechanism -- disappear. The downside is that all the inlined code results in code expansion.
While method inlining is a good optimization tool for Java programs, you often can't use it in general circumstances. Imagine that a class has a method
foo()
that is called by another method,
bar()
. At compile time, there may be no other subclasses that replace
foo()
, so it would be perfectly fine to inline the instructions for
foo()
inside of
bar()
. But Java is flexible enough so that another subclass could come along later and override
foo()
. The Java compiler must be ready to deal with these situations, so no inlining can take place.
Java's designers realized this flexibility would have an impact on performance, so they provided a keyword that allows you to forgo this flexibility. I
f you declare a method
final
, you indicate to the compiler that no other subclasses will try to define their own version of the method. When the compiler knows this, it can inline processes. Any attempt to override a
final
method generates a compile-time error.
If you write Java code, you should make liberal use of the
final
keyword. Many programmers use short methods because it makes their code more readable. Unfortunately, this can significantly increase the execution time because of a method's calling overhead. The final keyword eases the burden.
The code fragment in
"Method Inlining"
shows a sample class called
Rectangle
. In this case, the
final
keyword isn't used, so the Java compiler can't inline the code for
FindArea()
into
FindPaintCost()
. If it did, the result would look like the function
FindPaintCostInline()
. While the result is that the method can't be optimized, it means you can override the
behavior of
FindArea()
in the future (to calculate the area of a cylindrical tank, for example). The trade-off, then, is either better performance or code flexibility. You must decide which is most important to the program's operation in that situation.
Although Java looks very similar to C++ on the surface, the language is intended to be used differently than conventional programming languages. What might work for a C++ program can impair performance in a Java program. A Java program with many small methods can hamper performance due to the method-calling overhead. Armed with this knowledge and the
final
keyword, you can improve the performance of your Java applets.
// Example of standard method calls and method inlining
class Rectangle{
double length; // The length of the rectangle.
double height; //Its height.
double FindArea(){
return length*height
}
double FindPaintCost(){
int area
area = FindArea()
r
eturn area*4
// Cost is 4 times the area.
}
final double FindPaintCostInline(){
int area
area = length*width
return area*4
// Cost is 4 times the area.
}
}
illustration_link (20 Kbytes)

Downloaded objects can link to other Java libraries dynamically and inherit or override their behavior.
BYTE consulting editor Peter Wayner's book Java and JavaScript Programming will be published by AP Professional. You can reach him at
pcw@access.digex.net
.