The Artima Developer Community
Sponsored Link

Chapter 1 of Inside the Java Virtual Machine
Introduction to Java's Architecture
by Bill Venners

<<  Page 5 of 5

Advertisement

Architectural Tradeoffs

Although Java's network-oriented features are desirable, especially in a networked environment, they did not come for free. They required tradeoffs against other desirable features. Whenever a potential tradeoff between desirable characteristics arose, the designers of Java made the architectural choice that made better sense in a networked world. Hence, Java is not the right tool for every job. It is suitable for solving problems that involve networks and has utility in many problem that don't involve networks, but its architectural tradeoffs will disqualify it for certain types of jobs.

One of the prime costs of Java's network-oriented features is a potential reduction in program execution speed compared to other technologies such as C++. Indeed, achieving satisfactory performance was one of the most frustrating struggles for Java developers in the first few years of Java's existence. Nevertheless, although the early experience with Java may have encouraged the developer community to conclude that Java is slow, this was not necessarily the right conclusion. Although Java can be slow, it isn't inherently slow. As virtual machine technology has advanced, great strides have been made in performance--even so far as to bring Java performance on par with natively compiled C.

The first Java virtual machine that appeared in 1995 executed bytecodes with an interpreter, a simple technique that yields very poor performance. Before long, just-in-time compilers appeared that greatly improved Java's performance compared to interpreters, but still left Java performance well behind natively compiled C++. With the most recent advances in virtual machine technology, however, Java's speed penalty is diminishing significantly if not vanishing altogether. Advanced techniques such as adaptive optimization have enabled Java programs to run at speeds comparable to natively compiled C.

Although the recent advances in Java performance are very good news, they don't necessarily signal the end of developer frustrations about Java performance. The trouble for developers is that, even though certain Java virtual machine implementations may yield stunning performance, developers can't always select which virtual machine their Java programs will run on. One of the promises of Java's architecture is that a Java program will run "anywhere," and that also means on any Java virtual machine. If you are writing a server application in Java intended for in-house use, you may be able to select which virtual machine implementation your application will run on. But as soon as you have multiple customers for your Java program, you will likely need to get your program to have acceptable performance on many virtual machine implementations. And in a world consisting of the kind of distributed systems encouraged by Java's architecture, with code and objects flying over the network from one virtual machine to another, developers basically lose all control over which virtual machine implementations their programs will run on.

Ultimately, whether or not performance will be a problem for you, and how you would go about dealing with that problem, depends on what exactly you are trying to do. Fortunately, Java is a very flexible tool, giving you many ways to deal with potential performance troubles. If, for example, what you need to provide is in effect a monolithic executable (such as a word processor or server process), you could:

Probably the most powerful way to manage performance of a monolithic application is by being able to pick the virtual machine yourself, but executing part or all of your program natively may be the best approach in some situations.

Compiling a Java program to a monolithic executable, which is sometimes referred to as "ahead-of-time compiling," can help improve performance, but usually at the cost of making it impossible for the program to use Java's dynamic extension capabilities. Ahead-of-time compiling performs static, not dynamic, linking. It yields fully linked, monolithic native executables that don't usually have the capability to bring in and dynamically link to new types at run time. For Java programs that wouldn't use dynamic extension anyway, however, ahead-of-time compiling should yield an executable program that behaves exactly like the program would if executed on a traditional virtual machine. Because many embedded systems have no need for dynamic extension and usually have resource constraints, ahead-of-time compiling is often used to compile a Java program to a native executable image that can be burned into ROM for an embedded system. Ahead- of-time compiling can also be used for a desktop application, so long as it doesn't use dynamic extension. If you are struggling to solve performance problems of a relatively standalone Java program that doesn't use dynamic extension, ahead-of-time compiling may be able to help.

Managing performance becomes more difficult, however, when you are developing not a monolithic application, but a distributed system, especially one in which code and objects will be moving from virtual machine to virtual machine. This kind of object-oriented network programming is, after all, one of the big promises of Java's architecture. In such cases, the best way to manage performance is in the way you design your system. Here you must resort to traditional mechanisms for improving performance, such as minimizing network traffic, selecting the best algorithm, and other standard approaches to performance tuning in any language.

Although program speed is a concern when you use Java, there are ways you can address it. By appropriate use of the various techniques for developing, delivering, and executing Java programs, you can often satisfy end-user's expectations for speed. As long as you are able to address the speed issue successfully, you can use the Java language and realize its benefits: productivity for the developer and program robustness for the end-user.

Besides performance, another tradeoff of Java's network-oriented architecture is the lack of control of memory management and thread scheduling. Garbage collection can help make programs more robust, which is a valuable security guarantee in a networked environment, but garbage collection adds a level of uncertainty to the runtime performance of the program. You can't always be sure when or if a garbage collector will decide it is time to collect garbage, nor how long it will take. In addition, the Java virtual machine specification discusses thread scheduling in very general terms. This looseness in the specification of thread behavior helps make it easier to port the Java virtual machine to many different kinds of hardware. Although virtual machine portability is important in a networked environment, the vague specification of thread scheduling leaves programmers with little knowledge, and no control, of how their threads will be scheduled. This lack of control of memory management and thread scheduling makes Java a questionable candidate for software problems that require a real-time response to events.

Still another tradeoff arises from Java's goal of platform independence. One difficulty inherent in any API that attempts to provide cross-platform functionality is the lowest-common- denominator problem. Although there is much overlap between operating systems, each operating system usually has a handful of traits all its own. An API that aims to give programs access to the system services of any operating system has to decide which capabilities to support. If a feature exists on only one operating system, the designers of the API may decide not to include support for that feature. If a feature exists on most operating systems, but not all, the designers may decide to support it anyway. This will require an implementation of something similar in the API on operating systems that lack the feature. Both of these lowest-common-denominator kinds of choices may to some degree offend developers and end-users on the affected operating systems.

What's worse, not only does the lowest-common-denominator problem afflict the designers of a platform independent API, it also affects the designer of a program that uses that API. Take user interface as an example. The AWT attempts to give your program a user interface that adopts the native look on each platform. Nevertheless, you might find it difficult to design a user interface in which the components interact in a way that feels native on every platform, even though the individual components may have the native look. So on top of the lowest-common-denominator choices that were made when the AWT was designed, you may find yourself faced with your own lowest-common-denominator choices when you use the AWT. The Swing library gives you more options, but ultimately you still have to wrestle with differences in user expectations when you design a cross-platform user-interface.

One last tradeoff stems from the dynamically linked nature of Java programs combined with the close relationship between Java class files and the Java programming language. Because Java programs are dynamically linked, the references from one class file to another are symbolic. In a statically-linked executable, references between classes are direct pointers or offsets. Inside a Java class file, by contrast, a reference to another class spells out the name of the other class in a text string. If the reference is to a field, the field's name and descriptor (the field's type) are also specified. If the reference is to a method, the method's name and descriptor (the method's return type, number and types of its arguments) are specified. Moreover, not only do Java class files contain symbolic references to the fields and methods of other classes, they also contain symbolic references to their own fields and methods. Java class files also may contain optional debugging information that includes the names and types of local variables. A class file's symbolic information, and the close relationship between the bytecode instruction set and the Java language, make it quite easy to decompile Java class files back into Java source. This in turn makes it quite easy for your competitors to borrow heavily from your hard work.

While it has always been possible for competitors to decompile a statically- linked binary executable and glean insights into your program, by comparison decompilation is far easier with an intermediate (not yet linked) binary form such as Java class files. Decompilation of statically-linked binary executables is more difficult not only because the symbolic information (the original class, field, method, and local variable names) is missing, but also because statically-linked binaries are usually heavily optimized. The more optimized a statically-linked binary is, the less it corresponds to the original source code. Still, if you have an algorithm buried in your binary executable, and it is worth the trouble to your competitors, they can peer into your binary executable and retrieve that algorithm.

Fortunately, there is a way to combat the easy borrowing of your intellectual property: you can obfuscate your class files. Obfuscation alters your class files by changing the names of classes, fields, methods, and local variables, but without altering the operation of the program. Your program can still be decompiled, but will no longer have the (hopefully) meaningful names you originally gave to all of your classes, fields, methods, and local variables. For large programs, obfuscation can make the code that comes out of the decompiler so cryptic as to require nearly the same effort to steal your work as would be required by a statically-linked executable.

Conclusion

So what is the main point of Java's architecture? As shown in this chapter, the Java programming language is a very general-purpose tool that has distinct advantages over other technologies. In particular Java can yield better programmer productivity and improved program robustness with, for the most part, acceptable performance compared to older programming technologies such as C and C++. Yet the main focus of the design of Java's architecture was not just to make programmers more productive and programs more robust, but to provide a tool for the emerging network-centric computing environment. Java's architecture paves the way for new network-oriented software architectures that take full advantage of Java's support for network-mobility of code and objects.

The Resources Page

For links to more information about the material presented in this chapter, visit the resources page at http://www.artima.com/insidejvm/resources.

<<  Page 5 of 5


Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use