The Java Language Environment

Architecture Neutral, Portable, and Robust


With the phenomenal growth of networks, today's developers must "think distributed". Applications--even parts of applications--must be able to migrate easily to a wide variety of computer systems, a wide variety of hardware architectures, and a wide variety of operating system architectures. They must operate with a plethora of graphical user interfaces.

Clearly, applications must be able to execute anywhere on the network without prior knowledge of the target hardware and software platform. If application developers are forced to develop for specific target platforms, the binary distribution problem quickly becomes unmanageable. Various and sundry methods have been employed to overcome the problem, such as creating "fat" binaries that adapt to the specific hardware architecture, but such methods are not only clumsy but are still geared to a specific operating system. To solve the binary-distribution problem, software applications and fragments of applications must be architecture neutral and portable.

Reliability is also at a high premium in the distributed world. Code from anywhere on the network should work robustly with low probabilities of creating "crashes" in applications that import fragments of code.

This chapter describes the ways in which Java has addressed the issues of architecture neutrality, portability, and reliability.

4.1 Architecture Neutral

The solution that the Java system adopts to solve the binary-distribution problem is a "binary code format" that's independent of hardware architectures, operating system interfaces, and window systems. The format of this system-independent binary code is architecture neutral. If the Java run-time platform is made available for a given hardware and software environment, an application written in Java can then execute in that environment without the need to perform any special porting work for that application.

4.1.1 Byte Codes

The Java compiler doesn't generate "machine code" in the sense of native hardware instructions--rather, it generates bytecodes: a high-level, machine-independent code for a hypothetical machine that is implemented by the Java interpreter and run-time system.

One of the early examples of the bytecode approach was the UCSD P-System, which was ported to a variety of eight-bit architectures in the middle 1970s and early 1980s and enjoyed widespread popularity during the heyday of eight-bit machines. Coming up to the present day, current architectures have the power to support the bytecode approach for distributed software. Java bytecodes are designed to be easy to interpret on any machine, or to dynamically translate into native machine code if required by performance demands.

The architecture neutral approach is useful not only for network-based applications, but also for single-system software distribution. In today's software market, application developers have to produce versions of their applications that are compatible with the IBM PC, Apple Macintosh, and fifty-seven flavors of workstation and operating system architectures in the fragmented UNIX® marketplace.

With the PC market (through Windows 95 and Windows NT) diversifying onto many CPU architectures, and Apple moving full steam from the 68000 to the PowerPC, production of software to run on all platforms becomes almost impossible until now. Using Java, coupled with the Abstract Window Toolkit, the same version of your application can run on all platforms.

4.2 Portable

The primary benefit of the interpreted byte code approach is that compiled Java language programs are portable to any system on which the Java interpreter and run-time system have been implemented.

The architecture-neutral aspect discussed above is one major step towards being portable, but there's more to it than that. C and C++ both suffer from the defect of designating many fundamental data types as "implementation dependent". Programmers labor to ensure that programs are portable across architectures by programming to a lowest common denominator.

Java eliminates this issue by defining standard behavior that will apply to the data types across all platforms. Java specifies the sizes of all its primitive data types and the behavior of arithmetic on them. Here are the data types:


8-bit two's complement
16-bit two's complement
32-bit two's complement
64-bit two's complement


32-bit IEEE 754 floating point
64-bit IEEE 754 floating point


16-bit Unicode character

The data types and sizes described above are standard across all implementations of Java. These choices are reasonable given current microprocessor architectures because essentially all central processor architectures in use today share these characteristics. That is, most modern processors can support two's-complement arithmetic in 8-bit to 64-bit integer formats, and most modern processors support single- and double-precision floating point.

The Java environment itself is readily portable to new architectures and operating systems. The Java compiler is written in Java. The Java run-time system is written in ANSI C with a clean portability boundary which is essentially POSIX-compliant. There are no "implementation-dependent" notes in the Java language specification.

4.3 Robust

Java is intended for developing software that must be robust, highly reliable, and secure, in a variety of ways. There's strong emphasis on early checking for possible problems, as well as later dynamic (run-time) checking, to eliminate error-prone situations.

4.3.1 Strict Compile-Time and Run-Time Checking

The Java compiler employs extensive and stringent compile-time checking so that syntax-related errors can be detected early, before a program is deployed.

One of the advantages of a strongly typed language (like C++) is that it allows extensive compile-time checking, so bugs can be found early. Unfortunately, C++ inherits a number of loopholes in its compile-time checking from C. Unfortunately, C++ and C are relatively lax, most notably in the area of method or function declarations. Java imposes much more stringent requirements on the developer: Java requires explicit declarations and does not support C-style implicit declarations.

Many of the stringent compile-time checks at the Java compiler level are carried over to the run time, both to check consistency at run time, and to provide greater flexibility. The linker understands the type system and repeats many of the type checks done by the compiler, to guard against version mismatch problems.

The single biggest difference between Java and C or C++ is that Java's memory model eliminates the possibility of overwriting memory and corrupting data. Instead of pointer arithmetic, Java has true arrays and strings, which means that the interpreter can check array and string indexes. In addition, a programmer can't write code that turns an arbitrary integer into an object reference by casting.

While Java doesn't pretend to completely remove the software quality assurance problem, removal of entire classes of programming errors considerably eases the job of testing and quality assurance.

4.4 Summary

Java--an architecture-neutral and portable programming language--provides an attractive and simple solution to the problem of distributing your applications across heterogeneous network-based computing platforms. In addition, the simplicity and robustness of the underlying Java language results in higher quality, reliable applications in which users can have a high level of confidence. The next chapter contains a brief discussion of Java's interpreted implementation.