This is the first in a two-part series on the benefits of using the Java development and runtime environment for embedded computing. Java, with its “write once, run anywhere” paradigm, is ideal for embedded computing because of its portability, reliability, security, and Internet capabilities. However, Java can present some challenges for embedded-systems developers.
First, speed – Java applications are inherently slower than applications compiled into native machine code and embedded processors are generally less powerful than those found on desktops. There’s no direct memory access and no interrupt handling, two features usually required for developing low-level software to control hardware, and Java can’t easily run in real time, to name only a few of the issues. Despite its limitations, Java can be used effectively for embedded systems. In this article, I share Wind River’s experience in embedded real-time computing to help developers overcome some of the common pitfalls inherent in the Java development environment. Part 2 will help you determine if Java is right for your embedded development project.
Java for the Embedded World
Unlike desktop systems, embedded systems use different user interface technologies; have significantly smaller memories and screen sizes; use a wide variety of embedded processors; and have tight constraints on power consumption, user response time, and physical space. The original Java Developer’s Kit (JDK) technology was designed for desktop environments with powerful processors, large disks, and large available memory spaces. Consequently, the full JDK architecture is not suitable for many applications in the embedded world. However, Sun has also introduced versions of the first iteration of Java (JDK 1.1 or Java 1) for the embedded world, namely EmbeddedJava and PersonalJava. The newest iteration, SDK 1.2 and higher (1.3, 1.4) or Java 2, is grouped into three editions, one of which is the Java 2 Micro Edition (J2ME) aimed at the consumer electronics and embedded market. EmbeddedJava, PersonalJava, and J2ME provide standard, platform-independent Java development environments that reduce costs and shrink development cycles for Java applications and applets running on embedded devices.
EmbeddedJava is a scalable and configurable environment suitable for low-end embedded devices with dedicated functionality and limited memory. It’s ideal for closed system devices that don’t require Web browsing capabilities and don’t expose application programming interfaces (APIs) to the outside world. EmbeddedJava includes tools that allow developers to configure and compile runtime environments that contain only those fields and methods necessary for a particular application’s needs. An executable image of a complete EmbeddedJava environment can be generated and placed in the embedded system’s ROM. A dedicated tool chain creates optimized application executables known as ROMlets, which can be programmed into the device’s ROM, and patchlets, enhanced ROMlets that can be upgraded in the field. Developers can use EmbeddedJava for a variety of products, including process controllers, instrumentation, office printers and peripherals, and networking routers and switches.
PersonalJava is an upward-compatible subset of Java dedicated to consumer and embedded devices, and specifically designed for building network-connectable consumer devices for home, office, and mobile use. It consists of the JVM and a subset of the JDK 1.1 APIs, including core and optional APIs and class libraries. PersonalJava includes the specific tools and APIs required by embedded applications in resource-limited environments. Examples of devices suitable for the PersonalJava application environment include mobile handheld devices, set-top boxes, game consoles, and smartphones.
J2ME, designed for the development of such devices as digital cellular phones, pagers, personal digital assistants, digital set-top boxes, and retail payment terminals, defines vertical platforms called profiles that sit on top of two different configurations. The connected device configuration (CDC) uses a 32-bit standard JVM and requires more than 2MB of memory. This configuration relies on some kind of connection to a network and on an underlying RTOS and C runtime environment. The connected limited device configuration CLDC) uses the 16- or 32-bit KVM and requires 256 512KB of memory. It doesn’t necessarily require a “persistent” network connection. Profiles within these configurations are integrated into the J2ME framework with each profile targeting a precise vertical market. As discussed, some mechanisms of the JVM and Java as defined in the desktop-oriented platforms (JDK 1.1 or Java 2) are not suitable for embedded systems. PersonalJava, EmbeddedJava, and J2ME define a framework for optimized embedded Java implementations, but these Java versions alone are not ideal unless coupled with some background and experience in the development of real-time and embedded applications. The key to success is in knowing how to architect Java technology specifically for an embedded device.
Java is a semicompiled language and therefore inherently much slower than native machine-code execution. This speed issue is exacerbated by the fact that embedded processors are usually less powerful than desktop ones. The easiest and most expensive way to enhance Java performance is to use a faster processor. Fortunately, there are other solutions. Just-In-Time (JIT) compilers for desktop JVMs accelerate the Java interpretation cycle by translating Java byte code into machine code on the fly. In their original form, JIT compilers are not suitable for use with embedded applications because they require a lot of dynamic memory. The compilers are also unable to reach the performance of traditionally compiled C/C++ code because they translate Java byte code into native code at runtime as opposed to buildtime. Developers can achieve a tradeoff between performance and memory footprint with dynamic adaptive compilers – JIT compilers customized for embedded applications – that perform statistical analysis of byte code prior to its translation into native machine code. Flash (or “pass-through” JIT) compilers aren’t embedded in the JVM like JIT compilers, instead they run separately on a network host as “compiling” servers. Upon a class download demand, the flash compiler compiles the requested Java bytecode and passes the resulting native code to the JVM. Flash compilers are still runtime compilers and as such, they’ll slow runtime execution to some extent. Plus, they deliver classes over a network during application execution, which can also slow execution speed. Ahead-of-time compilation translates Java source code to C code (losing the Java portability) or translates Java byte code to native machine code (retaining portability at the application level). Unlike JIT or flash compilers, ahead-of-time compilers work at compile-time and can achieve optimizations similar to those achieved by traditional compilers. Developers can avoid undesirable code expansion due to byte code-to-machine-code translation by compiling 20% of the most relevant Java code into native machine code, with the JVM interpreting the remaining 80%.
Vincent Perrier is Product Marketing Manager, Java Platforms, for the Wind River Platforms Business Unit in Alameda, CA. He can be found at http://www.windriver.com