EDN logo

Design Features


The scoop on Java

Markus Levy, Technical Editor


Java is a programming language. Java is object-oriented. Java is byte-code. Java is an operating environment. Java is processor-independent. Java is Internet and intranet. Java is marketing hype and an enabling technology. Java is all of the above and more.

  Although Java is best known for its role in animating Web pages, this new programming language has far-reaching possibilities that range from commercial transaction systems to set-top boxes to embedded applications. The number of Java-related products and companies involved in Java-related development is overwhelming. If you do a search for Java on Alta Vista (http://www.altavista.digital.com) and exclude references for coffee, you'll find 200,000 hits. The trick is to learn enough about Java to allow you to filter the vast amount of information and hype. Understanding the Java basics can help you discover if Java is your cup of tea.

Java's many meanings

  Java means something different to everyone. To Sun Microsystems, Java's developer, Java could mean fame and a very healthy income generated from a multitude of licensees. To others, Java represents a land of opportunity to be found in still uncharted waters. These people are looking for that great Java application that will help them make their fortunes. Microprocessor vendors view Java as another programming language for their devices to run. And, better yet, because Java is a processor-independent language (see the section entitled "The Java Virtual Machine"), Java may allow smaller µP vendors to grab market share from the big guys. Most, if not all, of these µP vendors are busy developing software interfaces (see the section entitled "Microprocessors on Java") that will execute Java on their µPs.

  Real-time operating system (RTOS) developers face a similar, but distinct, situation to that of the µP vendors. The situation is similar because RTOS developers must also provide their customers with the software interfaces that will enable Java execution within RTOS platforms. On the other hand, an RTOS must also deal with issues such as real-time interrupts and deterministic execution (see the section entitled "Running Java on an RTOS").

  To appreciate Java's potential applications and understand where Java doesn't fit, it's helpful to learn a few Java fundamentals. At the highest level, Java is a C++-like, object-oriented programming language. When you compile a Java program, the output is in the form of processor-independent binaries called byte-codes. Most byte-codes perform simple operations, such as add and call; however, some are fairly complex. For example, Java has four object-oriented procedure-call instructions that perform a series of operations. To run byte-codes on a specific platform, you must develop an interpreter that translates the byte-codes to the processor-specific machine codes. In other words, you can execute the byte-codes on any platform that has a corresponding translator. This situation is analogous to a Web browser that interprets the data contained in a hypertext markup language (HTML) file, regardless of processor or operating system (OS). This basic, yet very flexible, capability is expanding the horizon for existing applications and opening doors for many new applications.

  Java's most visible, but impractical, role to date has involved the animation of World Wide Web home pages. (We've all seen things such as dancing elephants and rotating spheres.) But, more practical Web-page applications are on the way. For example, Lynx Real-Time Systems has developed a Java-based configuration and build tool. This tool, located on Lynx's home page, is an interactive Java application that allows you to select the modules required to develop your application. As you click on each module, a scale shows you how much total memory the modules consume and then provides a cost estimate for the RTOS.

  Another growing application for Java is the delivery of business content (application and data) through intranets and over the Web to both browser- and nonbrowser-based client/server systems. Until now, the intranet and Internet were merely means for transferring data. Once you had the data, you had to use a local- or server-based application to process the data into the desired format.

  How do you determine whether an application is a Java candidate or should be a server-based application? The following example provides an explanation: Suppose you have an application that takes user inputs and searches through a million database entries to return data to the user. To run the search routines on the client in Java, you would need to download the million database entries to the client, which is possible but not practical. In this case, it's best to run the search routines on the server. However, for the same application, you could use Java to prefilter or process the data that the user types in (such as checking that all necessary fields have been entered) before passing the user data to the server-based application.

  In this case, Java provides four benefits: The user receives a better interactive response, because the data doesn't have to make the client-server round trip before getting back a message such as "Enter field x"; the server doesn't have to service all the requests that accompany the preprocessing, because Java offloads them to the user's machine; Java allows you to offer the user a more sophisticated user interface; and after the server performs the database search and returns raw data to the user, you can use a Java applet that allows the user to view that data in a variety of ways (such as a pie chart, bar chart, line graph, or 3-D visualization).

  Consumer electronics and embedded systems also benefit from Java. You can use this processor-independent language to control everything from home appliances to enterprise messaging systems. Figure 1 shows an interesting and practical example for using Java. The application can be a printer or any type of embedded system. Traditionally, a printer manufacturer must include a floppy disk that contains device drivers for different processors and OSs, which can be a logistical nightmare, especially when it's necessary to ship revisions. Instead, more and more embedded systems are using Web protocols for GUIs, and the systems no longer require client-side software. According to Figure 1, the RTOS kernel manages a transmission control protocol/Internet protocol (TCP/IP) driver and the main application software. Adding http support allows a Web browser to access an HTML file stored in the embedded system. The embedded-system developer can add a Java applet to this HTML file to provide an interactive GUI.

  One area where Java is not a friendly word is in deeply embedded applications. Java has tremendous system-resource overhead, and it doesn't allow you to read an A/D converter or access hardware-specific features. In addition, embedded applications may not benefit from Java's features, such as processor neutrality.

The Java Virtual Machine

  A Java implementation involves four interrelated technologies, as defined by Sun Microsystems, including an object-oriented programming language and a byte-code format. The third technology consists of base classes. Base classes are a set of platform-independent, system service application programmer interfaces (APIs) (see box, "Java: the programming language"). The fourth piece, and, perhaps the most complex, is the Java Virtual Machine (JVM). The JVM comprises an execution engine, a just-in-time (JIT) compiler, a loader, a byte-code verifier, a garbage collector, base classes, and debug interfaces (Figure 2).

  In the simplest sense, the JVM simulates a virtual Java CPU at runtime. As an execution engine, the JVM performs byte-code interpretation for a specific processor, analogous to a software emulator. This virtualizing process can result in a Java program that runs 10 to 20 times slower than a comparable program that uses compiled C or C++ code. To help boost Java performance, you can add a JIT compiler to the execution engine.

  The JIT compiler avoids translating the same byte-code instructions each time they are encountered. Instead, when the system runs a Java program, the JIT compiler translates an entire Java class into machine-specific code and caches the result on the fly. As the JIT compiles more classes, the Java application spends more time in pure native-assembly code. Depending on the amount of system memory you can afford to have, you may or may not have enough space to cache all the classes used by a Java application. In this case, you may have to employ a "class-caching" algorithm to determine how to manage the memory allocation. Alternately, it's possible to use your system's hard drive to cache the JIT compiled code (if a hard drive is present).

  Although JIT compilation can increase execution speed five to 10 times, these speeds do not equal compiled C performance. The JIT-compiled code falls short on compiled C performance because the Java application spends time executing byte-codes in the interpreter until the JIT compilation completes. Switching between byte-codes and native machine codes causes overhead, such as a parameter passing between the two languages. Also, because the JIT compiler only processes one Java class at a time, the benefits of a C compiler's global optimizations are lost. JIT compilation also has nonperformance-related drawbacks. For example, a JIT compiler requires extra code space for the compiler and for the byte-codes before they are translated.

  Before JIT compilation can occur, your system must load the Java class. As your system executes a Java application, it may require a Java class that hasn't been loaded. When this occurs, a JVM's loader locates the class code required to continue running the Java application and transfers it into memory. The loader searches both the local and remote machines for the most recent version of the requested class file. This runtime loading process is referred to as "dynamic linking."

  After the loader loads the class—but before it passes the code to the JIT compiler—the Java byte-code passes through a byte-code verifier (Figure 3). The lack of pointers, combined with Java's strong object typing, make it possible to verify a Java program before executing it. The verifier confirms that the byte-code doesn't break any Java-language constraints. However, this verification process is not foolproof for ensuring virus-free Java applications. Knowledgeable hackers have a way of finding a language's vulnerability. As Javasoft's documentation warns: Users must be wary of executing any code that comes from untrusted sources. So, if you download from the Web, who can you trust?

  System performance depends on memory-resource management. In languages such as C++, programmers must keep track of used memory blocks. When an object no longer uses a block, the programmer must include code (malloc and free) that frees that memory space for reuse. The larger the program, the more difficult the memory management becomes; memory leaks and bugs usually occur.

  The JVM contains an automated memory reclaimer, called the garbage collector, which seeks and destroys unused Java objects. You can configure the Java runtime system to run the garbage collector in a low-priority, nonterminating thread. Garbage collection ideally runs during CPU idle cycles or when inadequate memory resources trigger an exception.

  Similar to a disk drive, garbage collection produces memory-space fragmentation, known as "heap fragmentation" (Figure 4). The more fragmentation, the more often the
runtime system must call the garbage collector, which results in lower performance. The cure for fragmentation is heap compression. In lieu of pointers, the Java language uses symbolic handles for all memory accesses. Handles provide an extra level of indirection and avoid direct memory access. The JVM loads an object into memory, assigns it a handle, and stores the handle in a fixed memory area. When the garbage collector performs heap compression, the handle is updated to reflect the new location of the object memory. All future references through that handle access the object at its new memory address. Although hooks for on-the-fly heap compression are available in Java, this feature is implementation dependent. Sun's implementation of the garbage collector does not perform heap compression.

Running Java on an RTOS

  One of the most important features of an RTOS is its ability to run deterministically—especially for critical real-time functions, such as interrupts and context switching. But, the very nature of an interpretive language is not deterministic, and Java is no exception. One solution to this problem is to develop a hybrid system that contains the high-level benefits of Java and maintains the performance aspects of an RTOS.

  The modular design used by RTOSs, such as Integrated Systems' pSOS, Chorus Systems' Chorus/OS, or WindRiver's VxWorks, makes the hybrid approach possible. Starting with the kernel, you add various modules to build your system. The JVM provides hooks that allow you to treat it as another module and add it on top of the RTOS kernel. Within the JVM, you can put a wrapper around a Java class so you can link the class to another language, such as C. The more C code you use within the Java Base Platform (JBP), the higher the performance, but at the expense of portability. However, RTOS/JVM implementations are still portable across all the platforms supported by that RTOS.

  Modularity also helps to control an application's resource requirements. Memory usage numbers from WindRiver indicate that its VxWorks RTOS can provide a feature set to support the JVM and an embedded Java platform to within 20 to 30 kbytes of code. You can also scale VxWorks to support the entire JBP with a few hundred kilobytes of code.

  In WindRiver's implementation, the JVM runs within VxWorks. You can configure any normal RTOS tasks to run at higher priorities than the Java threads. This prioritization scheme allows real-time functions, such as an interrupt handler, to preempt any running Java threads, including garbage collection, resulting in a suitable real-time response.

  Garbage collection is one of the most significant Java runtime components that you must modify to accommodate a real-time system. In a desktop-computing platform, the system essentially comes to a halt and is uninterruptible while the JVM performs the memory cleanup. From the computer user's perspective, however, garbage collection is undetectable. But on a real-time system, you can have higher priority tasks that must occur, such as servicing a TCP/IP Ethernet driver or reading/writing to I/O devices to control a heartbeat machine. In such situations, uninterruptible garbage collection can be detrimental. Real-time systems require you to develop deterministic garbage collection.

  There are two types of garbage collection: synchronous and asynchronous. Synchronous garbage collection occurs periodically and is the method that most nondeterministic OSs use. For an asynchronous implementation, the RTOS kernel must have a mechanism to coordinate the appropriate time to perform garbage collection. In pSOS, a cleanup occurs when a task needs memory. To perform this cleanup, the kernel must decide if no higher priority tasks will be affected. Because pSOS has a priority preemptive OS kernel, it can quickly switch between a lower rate task, such as garbage collection, and the higher rate applications that perform the critical tasks. In order to do this, Integrated Systems had to modify Sun's JVM implementation and replace the task scheduler.

  Another area of Sun's JVM that must be modified for a more embedded focus is the loader. As an example, pSOS has its own loader that allows you to determine what and when to unload. The modifications that Integrated Systems made centered on the need to load objects from ROM to RAM (compared with the standard JVM, which loads from the Internet or disk). The pSOS implementation of the JVM also provides an option that runs the Java byte-code through the verifier. In a closed embedded application, byte-code verification is probably not necessary, and avoiding it improves performance.

Microprocessors on Java

  The $64,000 question is "What's the best processor for executing Java byte-codes?" Is it RISC or CISC? Is it a stack-based machine? Or, perhaps there is no "best processor" to push byte-codes through. Time may be the best judge. There's a port for almost every 32- and 64-bit µP architecture, so you can run your own benchmarks.

  With the exception of the dedicated Java chips from Sun, all µPs must run either an interpreter or a JIT compiler to process Java byte-codes. There are two basic methods for translating the byte-codes to the machine-specific code. One method uses a huge case statement; the translator handles byte-codes as data and performs a series of compares in an if-then-else format. Sun uses this method in its JVM. Alternatively, method two consists of shifting the byte-code to the left to create a table offset, adding the offset to the base address of a dispatch table, loading the address of the code fragment, and then branching to the code fragment—a huge amount of work, and you haven't even executed the real operation yet.

  You may surmise from the first method that an ARM processor may be good at such a task because of its ability to perform a conditional check and branch on every instruction. However, raw CPU performance may be the key. Or, perhaps a large cache size would be helpful to hold the dispatch table and the code fragments.

  To promote processor independence, Sun designed its JVM implementation to be stack-based to accommodate µPs that have few registers. To add two numbers using a stack-based architecture, you first push the numbers onto the stack, perform the add, and leave the result on the top of the stack, something like an HP calculator. Although a stack-based implementation is a neutral approach, most processors are register-based. Therefore, the original JVM ports experienced suboptimal performance when running the "out-of-the-box" Sun implementation. Furthermore, Sun's interpreter is big-endian biased, which hinders x86 CPU performance.

  Until recently, most companies making platform-specific JVM ports have taken advantage of the source code implementation available from Sun because it quickened their time to market. According to WindRiver (which currently makes JVM ports for SPARC, 68K, x86, and MIPS), creating a port for a specific µP consists of recompiling Sun's code and testing—about one day's work. Compiler companies such as Symantec and Borland originally took this approach. Obviously, performance characteristics depend on how good a particular compiler is for a particular target. Recently, however, Symantec and Borland have released newer Java compilers that yield much higher performance, because the byte-code interpreter is tuned to the processor architecture. Analysis may even suggest that a finely tuned JIT compiler performs just as well as a hardware Java implementation. Be careful when evaluating published benchmarks, though, because they may not contain the most recent data.

  The picoJava chip from Sun is a hardware implementation of the JVM. The chip, with its stack-based architecture, directly executes byte-codes, eliminating the need for a translator. PicoJava supports object-oriented method invocation, thread synchronization, and primitives for the garbage collector. Although these features may make Java applications run faster, that's all the chip does; it runs Java and nothing else. Brian Case, an independent consultant for µP design issues, claims that most applications that require Java will be satisfied by a conventional µP plus a software JVM; the others, which require high performance, won't be satisfied by a Java µP (Reference 1). However, Java chips will probably find a market in dedicated Java systems, such as network appliances or as coprocessors in systems connected to the Web.

  Patriot Scientific's PSC1000 µP is close to being a Java engine (as defined in the standard JVM specification) and, thus, may efficiently execute Sun's Java code. The PSC1000's architecture, called ShBoom, is a zero-operand, 32-bit architecture with a stacked ALU, local register stack, and 16 global registers. The processor is currently being designed into WebBook's (Birmingham, MI, http://www.webbook.com) portable Internet-access terminal.

  Other hardware-oriented Java products may be forthcoming from companies such as Temic and SGS-Thomson. In the third quarter of 1997, Temic plans to introduce a version of its Sparclet processor, the TSC711, which will implement several Java-compatible features. For example, the TSC711 will have a new type of memory-management unit that performs object management and can be used as part of Java's garbage-collection mechanism. As part of its superintegrated products program, SGS-Thomson is developing some Java accelerated capabilities, which could be incorporated onto an ASIC along with its ST486 DX-Core.

Java: the programming language

  Java is an object-oriented programming language similar to C++. However, Java is simpler than C++ because Java leaves out the unnecessary features of high-level programming languages. For example, Java has no operator overloading, header files, preprocessor, structures, unions, multidimensional arrays, templates, or pointers.

  Many of C's clever uses revolve around the ability to manipulate pointers. Java's lack of pointers removes the largest causes of programming bugs, such as memory leaks. The lack of pointers also makes certain functions, such as garbage collection, easier to manage. Garbage collection uses symbolic handles to access objects in memory, thus replacing pointers (Figure 4). On the other hand, Java's lack of pointers makes the language less friendly for µCs that have index registers and can take advantage of special addressing modes.

  In Java, as with any object-oriented language, all code and data must exist within classes. A class is an entirely self-contained entity, including code and data, that carries out a particular program function. An object consists of one or more related classes. A huge benefit of using classes is the increase in programmer productivity—you can design classes to be reusable and multipurpose entities. On the other hand, in memory-constrained embedded applications, the benefits of object-oriented programming may be outweighed by its high overhead.

  Sun Microsystems provides a set of Java base classes, which, when combined with Sun's JVM, becomes the Java Base Platform (JBP). Base classes provide a consistent way for Java programs to access frequently needed services across different platforms. In other words, the JBP provides a minimal set of capabilities to ensure proper operation of any Java application. The JBP provides the functionality to support network and filing system access and GUIs that allow an application to interact with the user in a standard, platform-independent way. Each base class contains a platform-dependent portion of code, called peer interfaces. Peer interfaces allow the base classes to interact with the underlying operating system.

  In some applications, the ability to multitask is important. For example, if part of your program is accessing the disk drive, another part of the program can utilize the disk-drive latency time to interact with the user or continue a lengthy computation. Java has built-in capabilities, through thread classes, that make it easy for you to develop multithreaded applications. The thread facilities of Java allow you to create multiple threads (limited only by memory resources) with multiple priorities that run concurrently.

Embedded Java is brewing

  Java. Will it survive the test of time? Most industry people think that Java will succeed. After all, what's wrong with an intelligent, well-thought-out programming language? However, there are lingering concerns that Java is too slow, takes up too much memory, and costs way too much to license it from Sun. But, many companies are working on products that should solve most of the problems associated with Java. Here's a small sampling of what's in store.

  Cygnus is working on a GNU Java for embedded development. The company, known for its C compilers and customer support services, will provide two types of clean-room-created Java compilers. One compiler will take Java source code and turn it into native-machine code. This process will allow developers to use Java purely as a programming language and not as a language for creating Java applets. Cygnus believes there are many optimizations to make when translating to a register-based processor, and its compiler should produce good results. A second compiler will take byte-code and turn it into machine language. Unlike a just-in-time (JIT) compiler or interpreter, Cygnus's compiler is static. It will be used for running previously created Java applets on embedded platforms. Microtec is also developing a useful tool for embedded Java applications. The tool will provide Java support for Microtec's XRAY debugger tool.

  Sun Microsystems is also working on Java support for embedded applications. Sun's embedded Java platform is proposed to have a much smaller minimum functionality set than the desktop version, which will allow developers to build applications that have special constraints, such as a small memory footprint, no display, or no network connection. Sun has also said it would provide a new application programming interface (API) for device drivers written in Java. The implementation will be allowed to access a special Java class developed specifically for direct memory access, something that is not allowed anywhere else in Java. This API could be beneficial for establishing a standard among RTOSs.

For more up-to-the-minute Java information, here are a few interesting web sites:

Manufacturers of Java technologies

When you contact any of the following manufacturers directly, please let them know you read about their products on EDN's Website. Note: All Web addresses start with http:// unless otherwise noted.
Borland
Scotts Valley, CA
(408) 431-1000
www.borland.com
Integrated Systems Inc
Santa Clara, CA
(408) 980-1500
Patriot Scientific
Poway, CA
(619) 679-4428
www.ptsc.com
Symantec
Cupertino, CA
(800) 441-7234
www.symantec.com
Chorus Systems
Campbell, CA
(408) 879-4100
www.chorus.com
Lynx Real-Time
Systems Inc
San Jose, CA
(408) 879-3900
www.lynx.com
SGS-Thomson
Microelectronics
Lincoln, MA
(617) 259-0300
www.st.com
Temic Semiconductors
Santa Clara, CA
(408) 567-8220, ext 18
www.temic.de
Cygnus Support
Mountain View, CA
(415) 903-1400
www.cygnus.com
Microtec
Santa Clara, CA
(408) 980-1300
Sun Microsystems
Palo Alto, CA
(415) 473-7100
WindRiver Systems
Alameda, CA
(510) 748-4100

References

  1. Case, Brian, "Java virtual machine should stay virtual," Microprocessor Report, April 15, 1996, pg 14.
  2. Espeset, Tony, KickAss Java Programming, Coriolis Group Books, Scottsdale, AZ, 1996.
  3. Van der Linden, Peter, just Java, SunSoft Press, Mountain View, CA, 1996.

Acknowledgment

Special thanks for the extra help on this article go to Simon Waddington of WindRiver, Mitch Bunnell of Lynx Real-Time Systems, Steve Houtchens of Integrated Systems Inc, and Mary Bradburne of KVO.


   Technical Editor Markus Levy can be reached at (916) 939-1642; fax (916) 939-1650, e-mail markuslevy@aol.com

| EDN Access | feedback | subscribe to EDN! |


Copyright © 1996 EDN Magazine. EDN is a registered trademark of Reed Properties Inc, used under license.