<<

67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 17 CHAPTER 2 Java: Fat and Slow?

sk Java zealots if Java is slow, and watch them bristle with annoyance. “Of course not,” they will say. “Performance is perfectly adequate with A just-in-time and is even better with the new HotSpot tech- nology.” Ask them if Java is fat, and you will receive a similar response: “The Java runtime is a bit large, but that’s only because it provides so many useful APIs. You’ get that kind of size with any sys- tem.” And you know, they are not wrong, either. On server and desk- top platforms, Java does work. In particular, Java has made huge inroads as a server-side , due primarily to the advantages that we discussed in the introduction. As for performance issues, remember that in a server environment, installing more or faster CPUs or more memory or even spreading the load across sev- eral machines can often tackle these issues.

17 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 18

18 CHAPTER 2

Still, there is a perception among developers that Java is fat and slow, but its convenience outweighs its performance issues. This perception is a concern to us, however, because we are interested in writing Java programs that run on small computing devices. Unlike server or even desktop platforms, we cannot just throw more hardware at small- device performance problems. In this chapter, we look at why Java is fat and slow and how those char- acteristics affect our desire to run Java programs on small devices. First, we examine the architecture of Java. Then, we follow its evolu- tion from a language for writing platform-independent client applica- tions and finally to its current role as a language for server and enterprise programming.

The Architecture of Java

We start by looking at how Java works. The basic architecture has not changed since Java was first released, although there has been consid- erable effort to improve parts of it. If you are already familiar with the architecture, feel free to skip to the next section—although you might find a refresher course useful.

Overview

The code that runs a Java program is referred to generally as a Java runtime environment (JRE). A JRE is a combination of native (com- piled for a specific machine architecture) and Java code that is targeted for a particular . JREs are embedded in other applica- tions, such as Web browsers, or are available directly from a shell prompt or desktop user interface. A JRE is generally packaged as a set of shared libraries (the native code) and archives (the Java code). There are alternative packagings, however. Parts of a JRE can be incorporated directly into hardware, creating the so-called Java chips. Or, alternatively, Java programs—or parts of programs—can be compiled directly into native code, eliminat- ing sections of a conventional JRE. In all cases, however, the semantics of Java code must be preserved, so we will discuss things from the viewpoint of a conventional JRE. 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 19

Java: Fat and Slow? 19

Sun’s Runtime Environment

As we discuss the architecture of Java, we will use the term JRE in a generic sense. More specifically, however, JRE refers to Sun’s runtime environment, which is a downloadable, self-contained package that is meant for distribution with Java applications to be installed on machines that do not already have the capa- bility to run Java programs. We will discuss that JRE later in this chapter.

Packaging issues aside, a JRE consists of two major components: an execution engine and a pair of runtime libraries.

The Execution Engine

The execution engine is the core of the JRE and is responsible for load- ing and running Java programs. The requirements for the execution engine are contained in The Specification (JVMS), the official reference with which all implementations must conform in order to qualify as a JRE. The execution engine itself is split into several components: a virtual machine, a garbage collector, a class verifier, a class loader, and a native code interface.

Compilation versus Execution

There are two important documents that tool implementers—the programmers who write compilers, debuggers, runtime environments, and other applications that other programmers use—refer to when dealing with Java. The first is the JVMS. The second is The Java Language Specification (JLS). The JLS describes the syntax and semantics of the Java programming language, while the JVMS describes how to run Java programs. In general terms, the JLS is about compiling Java, while the JVMS is about executing Java. We will see that you can also use the JVMS to run programs that are written in other languages.

The Java virtual machine (often referred to as the VM or JVM) is the most visible part of the execution engine. In fact, the two are usually considered synonymous. For our discussions in this section, we will treat them as separate entities, but in the remainder of this chapter 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 20

20 CHAPTER 2

(starting with our look at the evolution of Java) and in the chapters that follow, we will use the terms VM and JVM to refer to the complete exe- cution engine. The various pieces that we discuss here are so closely tied together that for all intents and purposes, we can treat them as a single entity.

The Virtual Machine

The heart of the execution engine is the virtual machine. In computer science, a virtual machine is an abstract, idealized computer with its own instruction set. Virtual machines are implemented in software and are used for teaching and portability purposes—in any situation where it is useful to hide the messy details of an actual, physical architecture. Programs that are coded for a virtual machine can be run on any archi- tecture simply by porting the virtual machine—which is, after all, just another application—to the new architecture. This system is the basis of Java’s portability, along with the classfile format and the core run- time libraries.

Portability Is More Than a VM

Using a VM does not guarantee complete portability, however. True portability (usually referred to as binary portability) is achieved when the file format used to store the program is itself portable and the runtime libraries that the program depends on are present on each target architecture. Java’s write once, run any- where portability is based on those three components: the VM, the classfile for- mat, and the standard runtime libraries.

Bytecodes

The instruction set for the JVM is described in the JVMS. The instruc- tions are commonly referred to as , because many of them are a single byte in length. There are Java bytecodes for common oper- ations such as reading or writing memory, adding or subtracting num- bers, and branching—operations that any machine architecture supports. There are also bytecodes for implementing certain features that are specific to the Java language itself, such as the instanceof keyword. The JVM is built around the Java type model, so it under- stands the scalar types and object references that the language uses. 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 21

Java: Fat and Slow? 21

You can use a JVM to run programs that are written in other languages, but only if those languages can be compiled into Java bytecodes (the tight linkage between the VM and the Java language can make that diffi- cult). For an example of a non-Java language that can run on a JVM, refer to IBM’s NetREXX, which is available from http://www2.hursley. ibm.com/netrexx. A JVM is a stack-based machine that uses no registers, except for a pro- gram counter to track the instruction that is currently executing. Most bytecodes manipulate the stack in some way, using it as a place to retrieve input and to store results. For example, consider the following Java code:

int a = 5; int b = 3; int ;

c = a + b;

One possible translation into would be as follows:

bipush 5 // push single-byte value 5 on stack as int istore 0 // pop int value and store in first local variable bipush 3 // push single-byte value 3 on stack as int istore 1 // pop int value and store in second local variable iload 0 // load int from first local variable and push on stack iload 1 // do the same for second local variable iadd // pop top two ints from stack, add, and push result istore 2 // pop int value and store in third local variable

Using a stack makes it easy to implement the virtual machine on any architecture, because there is no dependence on specialized registers. It does increase the size of the compiled code, however, because of the necessity to push and pop values onto and from the stack. Consider this hypothetical example obtained by adding two registers, A and B, to the virtual machine:

// hypothetical only!

load_A 5 // load value 5 into register A store_A 0 // store register A into first local variable load_B 3 // load value 3 into register B store_B 1 // store register B into second local variable add_A_B // add A and B, storing result back in A store_A 2 // store register A into third local variable 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 22

22 CHAPTER 2

This imaginary example is 11 bytes long (each bytecode takes a single byte, and each of the operands takes another byte), while the previous example is 15 bytes long. This difference is not huge, but for larger pro- grams, it could make a noticeable difference in size. To alleviate this concern, the Java instruction set includes a number of short forms for commonly performed operations. A better compilation of the Java code would, in fact, be as follows:

iconst_5 // push constant value 5 on stack as int istore_0 // pop int value and store in first local variable iconst_3 // push constant value 3 on stack as int istore_1 // pop int value and store in second local variable iload_0 // load int from first local variable and push on stack iload_1 // do the same for the second local variable iadd // pop top two ints from stack, add, and push result on stack istore_2 // pop int value and store in third local variable

Instructions such as iload that take two bytes (one byte for the instruction and one for the operand) are replaced with equivalent one-byte instructions where the operand value is implicit (hard-coded into the instruction itself). Thus, iload becomes iload_, where n is a value from 0 to 3 (in other words, there are four separate iload variants in addition to the general form iload : iload_0, iload_1, iload_2, and iload_3). By using these implicit instructions, the can produce code that is 8 bytes long—a definite improvement over the 15-byte example that used the general (explicit) instruction forms.

Favor Implicit Instructions

One way to reduce the size of your compiled Java code is to favor the use of implicit instruction forms wherever possible. For example, if you can limit a method to four or fewer local variables, then the compiler can take full advan- tage of the implicit forms of instructions (such as aload, iload, fload, astore, istore, fstore, and so on). Limit yourself to three local variables on non-static methods, however, because the this reference (the object whose method is being invoked) is always stored as the first local variable (index 0). 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 23

Java: Fat and Slow? 23

Disassembling Java Bytecode

Seeing how Java code translates into bytecode is simple when you use the javap tool that comes with Sun’s Java Development Kit (JDK). We will talk more about the JDK later in this chapter. Simply ensure that the class you wish to disassemble is in the classpath, and invoke javap with the –c option. For example, try disassembling the code for java. lang.Object:

javap -c java.lang.Object

The output will look like the code shown in Figure 2.1, which we have abridged for readability by removing the wait methods. Notice how the methods implemented by the class are listed and followed by the code for each method. Native methods are not listed, of course, because there is no Java code associated with them. All of this informa- tion is extracted directly from the compiled class file—obviously a ver- bose format.

// Abridged output of javap -c java.lang.Object Compiled from Object.java public class java.lang.Object { static {}; public java.lang.Object(); protected native java.lang.Object clone() throws java.lang.CloneNotSupportedException; public boolean equals(java.lang.Object); protected void finalize() throws java.lang.Throwable; public final native java.lang.Class getClass(); public native int hashCode(); public final native void notify(); public final native void notifyAll(); public java.lang.String toString(); // removed the three variants of wait() }

Method static {} 0 invokestatic #21 3 return (continues)

Figure 2.1 Sample output for javap. 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 24

24 CHAPTER 2

Method java.lang.Object() 0 return

Method boolean equals(java.lang.Object) 0 aload_0 1 aload_1 2 if_acmpeq 9 5 iconst_0 6 goto 10 9 iconst_1 10 ireturn

Method void finalize() 0 return

Method java.lang.String toString() 0 new #13 3 dup 4 aload_0 5 invokevirtual #18 8 invokevirtual #19 11 invokestatic #24 14 invokespecial #16 17 ldc #3 19 invokevirtual #17 22 aload_0 23 invokevirtual #20 26 invokestatic #22 29 invokevirtual #17 32 invokevirtual #23 35 areturn

Figure 2.1 (Concluded.)

Java class files

A virtual machine written according to the specifications in the JVMS must accept its input in a format referred to as the classfile format. The classfile format is a portable binary format that describes the bytecode, strings, and other attributes of a single Java class. Each class file does not have to correspond to a single physical file, although that was a 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 25

Java: Fat and Slow? 25

common scenario in the early days of Java. Nowadays, it is more com- mon to package multiple class files into a single, compressed Java Archive (JAR) file for a variety of reasons. To the VM, however, the class files are still separate entities and are always treated individually. Portability is a key feature of the classfile format. You can take a class file compiled on a particular architecture and move it to another machine without worrying about differences in byte order or character encodings. The JVMS takes care of these and other potential road blocks. For example, strings are stored in UTF-8 format, which is a multi-byte encoding of Unicode, while individual data values that are larger than a single byte are always stored in big-endian (high bytes first) format. Class files are not compact because they store a lot of symbolic infor- mation: the name of the class, the names (including the parameter and return types) of methods, field names, the names of other classes and methods referenced by the class, and so on. Some of this information is optional and can be stripped out (debugging information, for example). But most of it is necessary because Java uses symbolic name resolution at run time in order to find classes, methods, and fields. Consider, for example, a simple class with no fields or methods apart from the auto- matically generated constructor:

public class EmptyClass { }

The compiled class file is about 200 bytes long (with no debugging information included), but if you disassemble it with javap, you will find the following:

Compiled from EmptyClass.java public class EmptyClass extends java.lang.Object { public EmptyClass(); }

Method EmptyClass() 0 aload_0 1 invokespecial #3 4 return

The bytecode for this class is exactly five bytes long. The rest of the file stores the strings for this class—the names of the class and its superclass, the name of its single method (constructor)—and other 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 26

26 CHAPTER 2

information. If EmptyClass were an interface instead of a class, the class file would be a bit smaller, but there would still be overhead. You should note that Java class files are standalone entities. There is no sharing of data between class files. In particular, this mean that the same strings—whether they are string literals, method names, or class names—are repeated across different files. This repetition and the fact that class files contain so many strings is one reason why they com- press so well when packaged together in JAR files.

Are Java VMs Slow?

JVMs are usually implemented as interpreters, which run programs by reading and executing the bytecodes one instruction at a time. This procedure is not a requirement; rather, it is merely the simplest imple- mentation. According to the second edition of The Java Virtual Machine Specification, So long as the class file format can be read and the semantics of its code is maintained, the implementor may implement these semantics in any way. What is “under the hood” is the implementor’s business, as long as the correct external interface is carefully maintained. In other words, VM implementors are free to do as they choose as long as they preserve program behavior. The most common alternative implementation is to convert Java bytecodes directly to native instruc- tions, for example. We will discuss that concept more when we explore the evolution of Java. People often make claims that Java is slow and blame this characteris- tic on its interpreted nature. By themselves, however, interpreters are not necessarily slow. A computer’s CPU is itself an , after all. Java’s slowness derives from other factors that are characteristics of the language itself, especially when compared to C or C++: Lack of pointers. The VM carefully controls all memory access. Objects are referred to by references, not by pointers, which enables the VM to ensure that the object is valid whenever it is involved in an operation. A reference is typically (but not necessarily) a handle to the actual object—an entry in a table of object pointers or a pointer to a pointer. This kind of implementation gives the garbage collector the flexibility to move objects around in memory in order to satisfy 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 27

Java: Fat and Slow? 27

memory requests, but it also adds an extra level of indirection when- ever an object is accessed. Runtime checks. Java bytecodes perform a number of runtime checks in order to catch errors as quickly as possible and to prevent memory corruption. If an object reference is required, for example, a Null- PointerException is thrown if the reference is null. Array indices are checked to make sure that they are within the bounds of the array; otherwise, an ArrayIndexOutOfBoundsException is thrown. Casts are checked in order to ensure that the object being casted extends or implements the required class or interface. A language such as C/C++ performs little runtime checking. Security checks. Security checks are runtime checks that deserve spe- cial mention because of the important part that they play in ensuring that a program is valid. Many of these are performed by the verifier, which we will discuss shortly, and this process slows program startup. The runtime libraries also perform various checks, which slow pro- gram execution. Synchronization primitives. Java was designed to support multiple threads of execution (whether or not the underlying operating system itself does), and the VM instruction set includes instructions such as monitorenter and monitorexit that directly support the syn- chronization semantics of the language. Any programmer who has worked with semaphores knows that acquiring a lock on a semaphore takes time and that too much synchronization will have a measurable effect on the program’s execution. For this reason, operating systems such as Windows offer faster synchronization primitives (critical sec- tions or functions) to perform simple atomic operations—anything to cut down the time it takes to acquire and release synchronization locks. Java’s synchronization facilities have always been particularly slow, and this concern is something that Sun has only recently addressed. Symbolic name resolution. As previously mentioned, a Java class file stores the names of each method in the class and each method called. The names are resolved at run time when the class is loaded and verified. Looking up methods by name instead of by position is how Java avoids the fragile base-class problem that is inherent in languages such as C++, where almost any change to a base class requires 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 28

28 CHAPTER 2

recompilation of every class that uses that class. This process does add overhead, however, by increasing the size of a class file and lengthening the time required to load a class in preparation for execu- tion. Non-static methods are also implicitly virtual (or invoked via indirect lookup), which is a difference from C++ (where methods must be explicitly marked as virtual). Slow array initialization. There are no instructions for initializing complete arrays in the Java VM. Instead, compilers must generate a series of bytecodes that initialize the array element by element. Con- sider, for example, the following array declaration:

int[] array = new int[]{ 10, 20, 30, 40, 50 };

The code to initialize the array elements looks as follows:

iconst_0 // index 0 bipush 10 // value 10 iastore // store iconst_1 // index 1 bipush 20 // value 20 iastore // store iconst_2 // etc. etc. bipush 30 iastore iconst_3 bipush 40 iastore iconst_4 bipush 50 iastore

For large arrays, this process generates a significant amount of code— not only bloating the size of the compiled class file, but also slowing program execution because of the extra code that must execute. The other components of the execution engine—the garbage collector, the class loader, the class verifier, and the native code interface—also play a part in slowing Java down, as we will see shortly. But removing or altering any of the factors that we have just discussed, or any of the components that we are about to discuss, would be counter-productive in most cases and would change important features of the Java lan- guage. You can only make careful and specific changes. 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 29

Java: Fat and Slow? 29

Array Initialization and java.lang.Character

Array initialization affects every Java program, even if the program does not declare any arrays. Why? The answer is because java.lang.Character defines a number of arrays for determining whether characters are letters or digits, upper-case or lower-case characters, and so on, and java.lang.Character is referenced (usually indirectly) by every program. In Java 1.02, these arrays were initialized in the usual way, but it was not a huge penalty—because at that point, java.lang.Character dealt almost exclusively with the ANSI subset of Unicode (the first 256 characters). With Java 1.1 came better support for program interna- tionalization, and to avoid excessive bloating due to array initialization, the required values were encoded in strings (by using the \u Unicode escape where necessary), and those strings were then used to manually initialize the arrays.

The Garbage Collector

A key feature of Java is its lack of explicit memory-deallocation primi- tives. All objects are allocated from the runtime heap by using the new keyword, but there is no keyword for deallocating objects. Instead, the garbage collector is responsible for searching through the runtime heap and reclaiming unused memory. This procedure can be done in several ways, but the most common method is to use a mark-and-sweep algo- rithm—marking objects as reachable or unreachable by following refer- ences from other objects (starting with objects that are static to a class or stored in a thread’s stack) and sweeping away all of the unreachable objects. As you might imagine, there are tradeoffs to using garbage collection instead of explicit object deallocation. The main advantages are as fol- lows: Objects are not deallocated prematurely. Unless there is a bug in the garbage collector (and this situation has been known to happen, but for the most part, they are conservative when marking objects as unreachable), an object that is in active use is never deallocated. There is no need to add reference-counting semantics to objects, for example, as is usually done with C++, nor is it necessary to agree on 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 30

30 CHAPTER 2

whom should deallocate an object when that object is passed between two or more methods. Objects are deallocated automatically. If an object serves no useful purpose, it is discarded without the programmer’s intervention. Here are the disadvantages: Garbage collection takes time. At various points throughout a pro- gram’s execution, the VM spends some time performing garbage col- lection. The garbage collection can occur on a regular basis by using a background thread, or it might only occur when the heap runs low on free space. Garbage collection can occur at inopportune times when using a mark-and-sweep-type algorithm, causing the program to pause for no apparent reason. Algorithms that work incrementally can avoid these pauses by spreading the work more evenly as the program runs, but that still slows execution. No control over object allocation and deallocation. Languages such as C++ give the programmer complete flexibility in arranging how objects are allocated and deallocated. Different heaps can be used for different objects, for example, or objects can be allocated directly on the program stack. Java provides a single heap for all allo- cations and does not permit allocations from the stack. Objects may never be freed. Garbage collection can occur infre- quently or not at all. There are no rules. Even when garbage collection does occur, some objects that are no longer needed by the program might not be freed, because active objects hold references to them. The garbage collector cannot do anything about these so-called dan- gling references.

Avoiding Dangling References

The easiest way to avoid dangling references is to explicitly set references to null whenever the referenced objects are no longer needed. Or, if you are using a full J2VM, consider using weak references—a way of marking active but not critical objects for possible deallocation by the garbage collector.

Pros and cons aside, there is little that a programmer can do to influ- ence the garbage collector. Apart from avoiding dangling references 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 31

Java: Fat and Slow? 31

and hinting as to when garbage collection should occur (by using Sys- tem.gc), the only real concrete influence that the programmer has is over the number of allocations. Allocate objects less often—and allo- cate smaller objects—in order to reduce garbage collection frequency. In particular, avoid garbage collector thrashing, which means continu- ously allocating small objects (such as event objects). Reuse existing objects whenever possible, and refer to the programming strategies dis- cussed in the next chapter.

The Class Loader

A virtual machine needs a way to locate the classes that it needs. This task belongs to the class loader. The class loader is perhaps the sim- plest part of the execution engine, but it has an important part to play. Its job is to find a class and load it into memory for use by the virtual machine. There are two kinds of class loaders. The system class loader is the basic class loader supplied by the execution engine. This loader always loads the core runtime classes (such as those in the java.lang pack- age) and usually loads the application classes, unless the application supplies its own class loader. In most implementations, the system class loader loads individual class files from a file system or from an archive file (such as a JAR or ZIP file), but it could just as easily load them from read-only memory (ROM), extract them from an image, or pull them across a network—whatever is appropriate for a particular execution engine. Non-system class loaders are referred to as user class loaders. A user class loader is a Java class that extends java.lang.ClassLoader in order to provide an application-specific method of locating class files. Web browsers, for example, install a class loader that obtains class files via a Hypertext Transport Protocol (HT TP) request back to a Web server. User class loaders are more limited than the system class loader, because all they do is locate and load class files. Validating the class, converting it to the VM’s internal format, and creating a Class object are tasks delegated to the system class loader. Class loaders maintain a cache of loaded classes to avoid the overhead of reloading classes whenever they are referenced. This measure also 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 32

32 CHAPTER 2

ensures that class initialization—initializing static fields and executing static initialization blocks—only occurs once. In general, then, a Class object cannot be garbage collected until the class loader that loaded it is itself garbage collected. This concern is not normally an issue, except when loading classes dynamically in order to group classes into name- spaces. Unlike languages such as C++, Java does not formally define what a namespace is—although it supports them indirectly through the use of class loaders. A namespace is the set of classes loaded by a specific instance of a class loader. Classes in different namespaces are disjoint: in other words, the same class loaded by two different class loaders is considered to be two different classes. Alternatively, two different classes with the same name can be loaded by two different class load- ers with no fear of conflict. This capability is useful as a way to run updated versions of classes without having to restart the virtual machine. Servlets, for example, are often loaded by Web servers in this way, enabling the programmer to replace the class files for a servlet without having to restart the server. To ensure that namespaces work as expected, once a class loader loads a class, any class it references is then loaded by using the same class loader. In theory, all of the classes that an application uses—including the core classes in the java.lang package—could be reloaded for each instance of a class loader. In practice, however, this situation does not occur, because one of the first things that a class loader does is del- egate the loading of a class to a parent class loader. Prior to Java 2, the parent class loader was always the system class loader. Java 2 intro- duced true parent-child relationships between class loaders. In either case, the class loader always asks the parent loader to load the requested class first, loading the class itself only if the parent cannot find it. For this reason, a Web browser will not download classes that are loaded locally by the VM’s system class loader (such as classes in the java.lang package). Namespaces have an important limitation. Classes in two different namespaces cannot invoke methods on each other. This condition would seem to make inter-namespace communication impossible, but class-loader delegation works around this limitation. If two classes are loaded by different class loaders but share a base class or interface in common—and the loading of that base class or interface is delegated to 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 33

Java: Fat and Slow? 33

a shared parent class loader—then the two classes can communicate by invoking the methods of the base class or interface. For example, if class A implements java.lang.Runnable, which is almost always loaded by the system class loader, then an object of class B in another namespace can invoke the run method of an object of class A by first casting that object to Runnable.

The Class Verifier

After a class is loaded (but before it is used), the class is processed by the class verifier. The verifier is an important part of the Java security infrastructure and ensures that any loaded classes are well-formed, that the bytecodes are all legal, that enough stack is allocated for each path throughout the code, that the bytecodes respect Java’s typing rules, and so on. Without the verifier, a malicious programmer could write byte- code that would compromise the system—a dangerous proposition if you allow unknown classes to be downloaded across a network. Verification is really just one step of a process referred to by the JVMS as linking. The other steps are preparation and resolution. Preparation creates the static fields of a class and initializes them to default values and can also perform other processing in order to prepare the class for execution. Resolution validates symbolic names, ensuring for example that the methods or fields that a class refers to actually exist. Linking is an apt name for the three steps, because they are akin to the linking process in languages such as C or C++—except that they occur at run time instead of in a separate, pre-execution step. As you might imagine, the linking process—verification in particular— adds overhead to the execution of a Java program. Extra time is required whenever a class is loaded, and on a slower processor, this sit- uation can significantly lengthen the startup time of a program.

The Native Code Interface

The final component of the execution engine is not really a component; rather, it is a bridge from Java to the underlying operating system. Any method declared as native is implemented in a language other that Java—typically C—and the native code interface defines how the VM 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 34

34 CHAPTER 2

calls that native code whenever the native method is invoked. The native code interface also defines how native code calls back into the execution engine in order to set and get field values, create new objects, invoke methods, and perform other operations. In Java 1.02, the native code interface was simple and non-portable. Java 1.1 introduced the Java Native Interface (JNI), a portable interface that made it easier to move native code from one machine to another. The portability comes at a price, however, because JNI is slower than its predecessor. When Sun Microsystems sued Microsoft for not com- pletely implementing the Java 1.1 specification, one of the claims was that Microsoft did not include support for JNI in its implementation, preferring instead to use its own interface called Raw Native Interface (RNI). Microsoft, in turn, claimed that RNI was significantly faster than JNI. For the end user, however, it meant that on Windows native code libraries, JNI could not be used with Microsoft’s VM (while RNI-based code could not be used with Sun’s VM). This, of course, contradicted Java’s message of write once, run anywhere.

Runtime Libraries

Like C or C++, the Java programming language depends on a of runtime code to interface with the system and to provide useful rou- tines for common programming tasks. The language itself is thus kept fairly small with few keywords. Writing portable Java programs, how- ever, depends on having a standard set of runtime classes that can be ported to different platforms along with the virtual machine. There are really two runtime libraries that accompany a virtual machine: one is a set of Java classes and the second is the native code required to completely implement those classes. Only the Java classes are visible to the programmer, of course, because the native code is called by invoking native methods on those classes. Unless the execu- tion engine is written entirely in Java, however, there is no way to avoid using the native runtime library, because key parts of the Java runtime library require the cooperation of the virtual machine. Creating threads is a concrete example. Whether an operating system thread is created or whether the VM simulates threads with its own context switching, the VM needs to know that a new thread is being started so that it can 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 35

Java: Fat and Slow? 35

allocate a new runtime stack and perform other important housekeep- ing chores. The border between the Java and native runtime libraries is blurry. Some things, such as thread creation or writing to the file system, can only be done by the native library. Other tasks, such as copying ele- ments from one array to another, could be done with Java or with native code but are done natively for performance reasons. The pro- grammer does not need to know if a method is native or non-native. The only real way to tell is by looking for the native keyword in the Java runtime library’s . The core of the Java runtime library is the java.lang package. A num- ber of its classes are directly referred to by the Java virtual machine:

■■ Object as the root of all class and interface hierarchies

■■ Class for defining information about a class

■■ ClassLoader for loading classes

■■ String for storing string constants

■■ Throwable for declaring and throwing exception information

The VM usually initializes these and other core classes upon startup. Many of the core classes are intertwined, so calling or using one class automatically causes one or more different classes to be loaded and ini- tialized.

The Evolution of Java

Although the architecture of Java has not changed much since its initial release, both the language and the runtime environment have evolved over time. Inner classes were introduced. The runtime libraries increased in size and complexity. Performance issues were (and are still) being addressed. In this part of the chapter, we follow Java’s evo- lution from its initial public release as Java 1.02 to its present-day Java 2 incarnations. Each major release of Java—1.02, 1.1, and 1.2 (renamed Java 2)—focused on a different kind of programming. 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 36

36 CHAPTER 2

Java 1.02: Client Programming

Java’s first public release was version 1.02. This release is often referred to as JDK 1.02, where JDK stands for Java Development Kit. At that time, a separate JRE did not exist. To install the JVM and the asso- ciated runtime libraries, you had to install the complete JDK (including the Java compiler and other development tools). Or, you had to run Java applications in a Web browser. Either way, Java was associated with client programming—programs that interacted with a user.

Applets

Embedding a virtual machine in a Web browser is what first brought Java to prominence. Downloading a program across the Internet and running it in a secure environment promised to usher in a new era of zero-install, on-demand application deployment. A new term, applet, was even coined to refer to these programs—which were in theory smaller than stand-alone Java applications.

Building the Java Sandbox

The Java sandbox is a combination of different features, all of which work together in order to ensure that applets do not perform risky operations. The first line of defense is the class loader, which ensures that an applet’s classes are only loaded from a single host. The class loader also ensures that different applets run in different namespaces. The second line of defense is the class verifier, which ensures that the downloaded code is well formed. The final defense is the secu- rity manager, which is a subclass of java.lang.SecurityManager that the browser installs and that the core runtime classes use to decide whether an applet has the necessary permissions in order to perform certain operations. These operations could include loading native code libraries, installing new class loaders, reading or writing to the file system, and so on. The security manager is not easily extended, however, and is not as fine-grained as some would like. Therefore, in Java 2, a new security mechanism was introduced that supercedes the security manager. The same concepts still apply, however, but now there is more flexibility. 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 37

Java: Fat and Slow? 37

In many ways, Java is an ideal language for applet programming. Not only does it offer binary portability (the capability to download and run class files on any client with a JVM), but it also makes it easy to build a sandbox in which to run classes distributed by untrusted third parties. The sandbox metaphor refers to the walls that a browser erects in order to prevent malicious applets from damaging the system, in the same way that a child playing in a sandbox is contained and is kept out of trouble. With Java 1.02, applets are always untrusted and are always run in a sandbox. Despite the original excitement, serious attempts at applet program- ming were discouraging. There were several reasons for this discour- agement: Long download times. A large applet took a long time to download, especially over the 28K or 56K Internet links that were common in the early days of Java programming. This concern is less of an issue now that more and more high-speed Internet connection options are available, but the problem is still noticeable. What is worse, the downloading was done by using the HTTP protocol, which meant opening a new connection to the web server for each class to be downloaded. Inflexible and limited user interface components. Java 1.02 defined a portable user interface tool kit called the Abstract Window- ing Toolkit (AWT), which used components from the underlying native user interface. Those components were by necessity limited to the capabilities that were common across the different operating-sys- tem platforms, however. This lowest-common-denominator approach is inflexible. Initially, AWT did not even provide control over basic elements such as tab order. The event model was also hardwired and hard to extend. Restrictive security model. Applets were restricted by the sandbox model. An applet could not store any information on the local machine, for example. The only way around this situation was to turn applet security off and give applets unfettered access to the machine, which of course was undesirable.

Of these, the long download time was the most problematic, because users were extremely impatient and found the waits frustrating. The waits could be reduced a bit by archiving the required files into an uncompressed (compression was not supported until Java 1.1) ZIP file, 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 38

38 CHAPTER 2

which the browser then fetched by using a single HTTP command. The most successful applets were either small or partitioned into discrete pieces that were dynamically loaded as needed, with a small splash screen at the beginning that asked the user to be patient while the applet finished loading. Another problem was that not all Web browsers were Java-enabled, meaning that if you included Java applets on your Web site, you were limiting the potential audience. This problem is less of an issue now, but at the time, the browser market was much more fragmented. Even among Java-enabled browsers, however, there were enough differ- ences—and bugs—between implementations to make the promise of write once, run anywhere seem a bit of a joke.

Applications

If applets were not a success, what about using Java to write stand- alone applications—programs that are run in a conventional manner— without a Web browser? As a general-purpose programming language, Java has much to offer. We discussed some of its features in the intro- duction—features such as layout managers, garbage collection, and built-in threading support. An application is not subject to applet secu- rity restrictions or encumbered by a lengthy download time. So, on the surface, Java should have succeeded early on as a general-purpose application language (despite the drawbacks that caused Java applets to fail as a programming model). Despite the advantages that are inherent in Java, there were three important and outweighing disadvantages that affected its use for gen- eral application programming. First, the Java 1.02 user interface com- ponents were limited and inflexible, making it hard to build applications with the rich functionality of native applications. Second, execution speed was also a concern. Java applications were slower and larger (for the reasons we discussed earlier in this chapter) than their native equivalents. And finally, you had to install a JDK on each machine in order to even run the applications—perhaps the biggest drawback of all because of size and licensing issues. Most programming languages never recover from these kinds of prob- lems. Java, however, gained a real following in the programming com- 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 39

Java: Fat and Slow? 39

munity, which saw it as an alternative to C++. Java also had a lot of marketing and development muscle behind it. So, instead of abandon- ing the language, Sun put out a new version of the language and started to de-emphasize its use as a language for client programming.

Java 1.1: Server Programming

Java 1.1 was much more of a change than the version number implied. Java 1.1 was more like a 2.0 than a 1.x release. Among the important changes were the introduction of inner classes, a new event model for user interface components, lightweight user interface components, internationalization, class reflection, object serialization, remote- method invocation (RMI), and others. (If you are curious, most of the changes are listed and explained on Sun’s Web site at http://java.sun. com/products/jdk/1.1/docs/relnotes/features.html). Some changes were separate from but dependent on Java 1.1,such as the Swing set of user interface components and the JavaBeans compo- nent model. Java 1.1 also brought the Java Database Connectivity (JDBC) database interfaces into the core runtime library. Although sev- eral of the changes had significance from a client-programming point of view, many of them were more important for server programming— programs that ran on a server machine with no direct user interaction, or the parts of a client program that communicated with the server. Let’s briefly examine the major changes, especially ones that are applic- able to server programming.

Just-in-Time Compiling

Of particular importance to Java 1.1 was the introduction of just-in-time compiling. A just-in-time (JIT) compiler converts bytecode to native code as a Java program runs. The compiler does not convert the entire program at once; instead, it works piece by piece and converts only the code that is about to be executed. Consider, for example, a simple code fragment:

int j = 0; for( int i = 0; i < 10000; ++i ){ j += i * 2; } 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 40

40 CHAPTER 2

Calling this fragment 1,000 times took about 2,000 milliseconds using JDK 1.1 but without JIT. Enabling the JIT reduced the execution time to about 60 milliseconds. (These numbers are meant to be representative only, not formal benchmarks.) For CPU-bound code, a JIT makes a sig- nificant difference. The biggest drawback, of course, is that extra time is required to compile the code before it is first executed. For this rea- son, a JIT can actually slow down the execution of code that is called only once or twice.

Disabling the JIT

Disabling the JIT in Java 1.1 is quite simple. Just use the –nojit option when using the java command. Doing this allows you to see complete stack traces when an uncaught exception is thrown. There is no equivalent option in Java 2, however. In order to disable the JIT, you have two choices: use the –Djava. compiler= option (in other words, set java.compiler to the empty string) when invoking the java interpreter, or rename or delete the runtime environ- ment’s JIT module. To perform the latter action, look for a shared library or dynamic link library (DLL) with a jit in its name. In Windows, for example, the JIT is a DLL called symcjit.dll.

Although Sun included a JIT with Java 1.1, it also announced that it was working on a more effective and ambitious acceleration technology called HotSpot. HotSpot was not released until after Java 2 was out, however, so we will defer our discussion of it until then.

Object Serialization

Objects in Java 1.1 can save their internal state to a stream in a process that we refer to as serialization. Not all objects are serializable, and not all the internals of the object are saved. The class gets to choose how its state should be saved. Once saved, a new object can later be recre- ated or deserialized directly from the serialization data. Unlike some persistence models, serialization is quite robust. Changes can be made to a class without losing the capability to recreate an object saved by using the old version of the class. 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 41

Java: Fat and Slow? 41

Serialization enables you to store Java objects for later reuse, such as in a database. This function also enables you to pass objects by value across a network connection.

RMI

One of Java’s great strengths is that its core runtime library includes classes for network communication. Java 1.1 extended this functional- ity with the concept of RMI, enabling a client Java application to invoke methods on an object running in a remote server. The client uses a proxy object to represent the real, remote object. Because the proxy and remote objects implement the same interface, the application calls methods on the proxy as if it were directly calling methods on the remote object. The client only requires a few changes in order to use RMI to obtain proxy instances and to handle network errors. Most of the work is done on the server, and even then it is not that hard. The RMI infrastructure hides all of the messy details. RMI does not pretend to be an interoperable protocol; rather, it is strictly for Java-to-Java distributed programming. A Java VM is required on both ends, and object serialization is used to pass parameters.

Database Connectivity

In Java 1.1, the JDBC classes and interfaces became part of the core Java run time. Databases play an important role in many applications, and JDBC provides a standard and vendor-independent way to commu- nicate with database servers via a JDBC driver. JDBC actually predates Java 1.1 but was defined after 1.02 was already out and did not come as part of the JDK, therefore requiring a separate download and installation. Because it was not part of the core runtime library, none of the 1.02-only Web browsers supported it, requiring the JDBC classes themselves to be downloaded with any applet that used them. As Java started being used extensively for server programming, work began to update the JDBC standard to include features such as connec- tion pooling and shared transactions in preparation for the next major release of Java. 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 42

42 CHAPTER 2

Browser Support for JDBC Prior to 1.1

The JDBC classes and interfaces are defined as part of the java.sql package, but browsers that support only Java 1.02 do not have them installed. Normally, this situation would not be a problem. The classes would be downloaded into the browser with the rest of the applet, but browsers will not (for security reasons) download classes in the java package or in any of its subpackages. The only way around this restriction is to rename the package and recompile the JDBC source files in order to produce a new set of identical but downloadable classes. Sun recommended that vendors should use the jdbc.sql package, and that is what most of them did. Once the newer browsers supporting Java 1.1 (and thus, the java.sql package) overtook the older browsers, the problem largely went away. For a while, though, vendors were supporting two versions of their JDBC drivers: one for use with Java 1.02, and one for Java 1.1 or later.

JavaBeans

Java 1.1 defined a new component model called JavaBeans for sharing and reusing objects. A JavaBean component (usually referred to simply as a bean) is a self-describing Java class that exposes attributes such as properties (with accessor methods), operations as methods, and notifi- cations as events. Beans are meant to be plugged into a development environment such as PowerJ, JBuilder, or Visual Café, which then asks the bean for a description of its features (through a process called introspection) and enables the user to manipulate the bean visually through development aids such as property lists. The JavaBeans specification is really directed at client development because it does not deal with server-side issues such as component reuse and transaction participation. This realm was left to the next ver- sion of Java and the Enterprise JavaBeans specification.

Servlets

As Web applications—server-based applications that generate Hyper- text Markup Language (HTML) pages as a user interface—gained in popularity, it became necessary for Sun to define a Web server exten- sion mechanism in Java as an alternative to languages such as Perl or 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 43

Java: Fat and Slow? 43

PHP3 or to interfaces such as ISAPI or NSAPI. The solution that Sun came up with was the servlet programming interface. Servlets are not part of the core Java run time but are instead consid- ered a standard extension to Java. The servlet classes are part of the javax.servlet package. Support for servlets was at first limited to Sun’s own Web server, which could easily support them because it was itself written in Java. Other Web servers had to first embed a Java VM in order to run servlets or else delegate the running to a separate server. The latter option is the most common way to run servlets within a non-JavaWeb server.

Browsers and the Java Plug-In

The virtual machine and runtime classes used by a Web browser are tightly coupled to the browser. Upgrading a 1.02-only Web browser to support Java 1.1 was not just a matter of copying some new files into the browser’s installation directory; rather, it required support from the vendor of the browser. New browser versions were released in order to support Java 1.1. Unfortunately for applet developers, users were in no hurry to install the newer browsers. Corporations were particularly reticent to upgrade company-wide installations without extensive testing or without a com- pelling reason. The applet developer had to choose between developing a 1.1-only applet and requiring users to upgrade their browsers or else stay with Java 1.02 and its limitations. To make matters worse, Netscape upgraded its browser in two steps: the first step added all of the core Java 1.1 classes except the new user interface classes, which were released in a later upgrade. The partial support for Java 1.1 was confusing to users. Microsoft’s browser had some differences, as well. In particular, Internet Explorer did not include support for JNI, prefer- ring instead Microsoft’s own native interface. This issue and others led Sun Microsystems to sue Microsoft for not living up to the terms of its Java licensing agreements. Because of these problems, Sun developed and released the Java Plug- in, a complete Java 1.1 runtime environment that could be installed as a Web browser extension. The user can easily install the Plug-in and can upgrade it at any time with bug fixes or new features without affecting 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 44

44 CHAPTER 2

the browser. Sun can release a new version of the Plug-in whenever it releases a new version of Java. From the developer’s point of view, the Plug-in is a mixed blessing. By requiring its use, developers ensure that their applets will run in a known and stable environment. The Plug-in is not available for all plat- forms and browsers, however, and it also requires changes to be made to the way in which the applets are embedded in an HTML page.

JRE

As we already discussed, the Java 1.02 runtime environment was only available as part of the larger JDK. With Java 1.1, Sun separated the runtime and development pieces of the JDK and offered the runtime environment in a separate download called (not surprisingly) the JRE. Besides being smaller than the JDK—about 3MB on Windows com- pared to the 20MB plus of the JDK—the JRE is also freely distributable, which enables developers to include it as part of their applications (instead of licensing the complete JDK from Sun or forcing the end user to obtain and install the JDK or JRE separately from the applica- tion). Once installed, the JRE also registers itself with the operating system, enabling other applications to discover its existence and to not have to install a second JRE (although that is still an option if an appli- cation requires a specific version of the runtime environment).

Java 2: Enterprise Programming

Unlike the transition from Java 1.02 to Java 1.1, the changes between Java 1.1 and Java 1.2 were not as radical. We are not saying that the changes were not significant, however. The introduction of the collec- tions classes, a new finer-grained security model, a new version of JDBC, and a new VM implementation are just a few. Developers did not have to rewrite their code so much as learn about a large number of new application programming interfaces (APIs). Many of these new APIs were focused on enterprise programming—programming for cor- porate environments where applications interacted with other non-Java programs and had special requirements related to high availability and scalability. And along the way, the Java platform was renamed and split into multiple editions. 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 45

Java: Fat and Slow? 45

New Names and New Beginnings

The collection of Java-related technologies from Sun was always referred to as the Java platform or just Java. Developers would refer to a Java platform by using the version number of its JDK. At times, it was not clear whether the term Java 1.x referred to the version of the Java programming language, the version of the virtual machine, or the ver- sion of the JDK. To clarify the situation and also to generate some excitement for its new release of the Java platform, Sun renamed the platform the Java 2 Platform. The JDK became the Java 2 Software Development Kit (SDK). The version number of the SDK—which many developers still refer to as the JDK (a term that we will continue to use throughout this book)—still followed the old numbering system, however. The first SDK was version 1.2, not version 2.0. The 2 in Java 2 does not really refer to a specific version; rather, it refers to a new marketing mecha- nism for Java technology. But the renaming did not stop there.

One Version, Three Editions

The sheer number of programming interfaces available in the Java 2 Platform is almost embarrassing. Many of these APIs were (and are) being defined by a new process directly involving interested parties external to Sun—a process referred to as the Java Community Process. This process was developed partly in response to criticisms that Sun was not capable of defining new interfaces quickly or objectively enough in order to satisfy market requirements. Sun realized early on that only a subset of the new classes defined by it or by the Java Community Process could become part of the core run- time environment. For one thing, the set of core classes was large enough already, as we will see in a later section, and some of the new classes would only interest small groups of Java programmers. This realization predates Java 2 and is why Java 1.1 introduced the concept of standard extensions, which was further formalized in Java 2. Another motivation for Sun not to include everything in the core run time was to generate more revenues by charging licensing fees for some of the more advanced features. Apart from charging fees to development 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 46

46 CHAPTER 2

tool vendors and other corporations that wanted to embed Java in their software or hardware in ways that were not covered by the free-of- charge JRE license, Java was in many ways a loss leader for Sun. Java was available for free to most developers as a way to promote and sell Sun Microsystems products and solutions. Separate licensable pack- ages would enable Sun to recoup more of its investment in Java. For these reasons, Sun split the Java 2 Platform into three editions, referred to as Standard, Enterprise, and Micro. Each edition targets a different set of application developers.

Java 2 Standard Edition (J2SE)

The Java 2 Standard Edition, or J2SE, targets conventional Java appli- cations. These are client applications running on desktop computers (either in a Web browser or as stand-alone applications) or server applications that do not require advanced APIs or interoperability with other languages or object models. In other words, J2SE is the core of what would have been called Java 1.2 and of any future versions. J2SE does not require any real change on the part of Java developers. Apart from API additions, many of the changes in Java 2 as compared to Java 1.1 are cosmetic:

■■ Swing is now part of the core run time.

■■ The JDK directory structure has been revised to separate the JRE from the rest of the JDK.

■■ A new version of Javadoc generates better API documentation.

■■ Standard extensions have been formalized.

There were many internal changes, however. Various performance enhancements were made, including the development of the HotSpot execution engine. HotSpot is based on technology that Sun acquired with much fanfare in response to criticisms that Java’s performance was extremely poor. Unlike a JIT, which indiscriminately compiles Java code upon first use, HotSpot profiles the code as it runs in order to determine which parts require optimization. The execution engine also includes an improved garbage collector and performs faster thread syn- chronization. You can read about HotSpot on Sun’s Web site at http://java.sun.com/products/hotspot/. There are currently two versions of HotSpot available, and these are referred to as the HotSpot Client 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 47

Java: Fat and Slow? 47

VM and the HotSpot Server VM. The former is tuned for use on clients (applications start more quickly), while the latter is tuned for server- side use (where applications are likely to run longer and can benefit from more optimization upon startup).

Java 2 Enterprise Edition (J2EE)

J2EE is a superset of J2SE that is geared specifically to enterprise pro- gramming. While J2SE can be used to build server programs, J2EE addresses the needs of corporations that need to write server-based programs that support thousands of clients and that must also interact with legacy (in other words, non-Java) systems. A full description of J2EE is beyond the scope of this book, but in broad terms, J2EE focuses on three elements: Application deployment. J2EE includes the tools to enable you to seamlessly and securely deploy applications to clients. This function includes support for thin-client computing by using Java Server Pages as well as enhancements to the Java Plug-in. Interoperability with legacy systems. J2EE adds support for CORBA/IIOP and extensible markup language (XML). Enhanced component-based development. By using Enterprise Jav- aBeans, developers can write scalable, transaction-aware components for use in application servers.

For more information, consult any of the many books about J2EE or go straight to the J2EE homepage at http://java.sun.com/j2ee/.

Java 2 Micro Edition (J2ME)

Finally, there is J2ME. This application is, of course, the focus of this book. We will defer all discussion of J2ME until Chapter 4, except to say that J2SE is simply too large for many small devices. J2ME slims it down by removing or rewriting key parts of the core runtime environment.

What Is Next for Java 2?

Many parts of the Java 2 platform are quite mature, and many are just developing. J2ME, for example, is still new, as is the Enterprise Edition. 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 48

48 CHAPTER 2

Both of them will be the focus of new development. New APIs for the Standard Edition are continually being developed through the Java Community Process, however, and you can expect to see occasional enhancements to J2SE—especially those that carry through to the other two editions. We do not foresee any radical changes in the imme- diate future.

The Devolution of Java

Although Java has evolved greatly in its transition from Java 1.02 to the Java 2 platform, in some sense it has also devolved from its roots as a language for programming consumer appliances. Consider, for exam- ple, the size of the Java runtime libraries. In JDK 1.02, the classes. zip file was 1.3MB in size (uncompressed) and contained just fewer than 600 classes. (All numbers are from Sun’s implementations for the Windows operating system.) The same file in JDK 1.1.7 is 8.34MB in size and contains more than 1,600 classes. (To be fair, though, the JRE 1.1 has a smaller set of classes because it excludes internationalization classes. But the JDK does include them all.) For JDK 1.2.2, the Standard Edition rt.jar file (the equivalent of classes.zip) is 9.7MB in size and contains more than 4,300 classes. Even without worrying about the rest of the execution engine, we can see that J2SE is simply too large for most small computing devices. The obvious solution is to start cutting components from the runtime environment. But what do you cut, and what do you leave in? Do you cut at the class level or at the method level? Can you reduce the inter- dependencies between the core classes? How do you deal with differ- ences in basic capabilities? Some devices have file systems and others do not, so do you leave java.io.File in or take it out? Can you per- form any of these actions and still have the program run in a normal Java environment (such as J2SE or J2EE)? We will answer these ques- tions and more in Chapter 4, which introduces J2ME. 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 49

Java: Fat and Slow? 49

Chapter Summary

In this chapter, we took a close look at the architecture of Java and its evolution from a client-oriented programming language to a platform that is capable of running enterprise applications. But before we dis- cuss J2ME, let’s take some time to discuss some useful programming strategies for small devices. 67957_Wiley_Giguere_CH02x 10/17/2000 4:50 PM Page 50