In-Depth

Simplify Your Real-Time Programming

Implement Real-Time Java''s high-level abstractions and easily and quickly develop predictable Java code for embedded systems.

Embedded developers have long looked at the Java language from across a great divide. For years the language was not able to run on many of the diverse, narrowly-focused 8- or 16-bit systems that were commonly used in embedded development. In addition, the Java language wasn't predictable enough to deal with real-time, real-world events.

Today this divide has all but disappeared. Standard hardware architectures are available in a wide array of small form-factor boards at low-enough costs for the majority of designs. Embedded Linux has become pervasive, and the Java code from your desktop or server can also run as is on x86, PowerPC and ARM architectures. Finally, Sun's Real-Time Java has matured, offering a predictable environment for developers dealing with time-based deadlines.

Inside Real-Time Java
Real-Time Java is a standard as defined by the Real-Time Specification for Java (RTSJ) within the Java Community Process. The RTSJ is formally correct from a real-time perspective and easier to use than standard C/C++ toolsets. The RTSJ defines how a real-time–capable Java-platform implementation should handle the following elements: scheduling, synchronization, memory management, asynchronous event handling and transfer of control, and physical memory access. In this article, you'll take a closer look at some of these elements.

Tasks, Costs and Deadlines
The RTSJ models a real-time application as a set of tasks, each with a cost and a deadline. The cost of a task is defined as the time the task takes to run (CPU time). The deadline for a task is defined as the time at which the task must be completed. The scheduler uses the cost to determine the latest time at which the task can be scheduled without missing its deadline.

A task can have a deadline-miss handler and a cost-overrun handler. If a task runs past its deadline, or the scheduler is not able to schedule the task before its deadline has passed, the appropriate handler is called. Information from the miss or overrun handler can be used in deployment to take corrective action, or to send performance or behavioral information to the user or a management application. By comparison, in standard Java language or C/C++, these types of failures may not become apparent until secondary or tertiary side-effects are observed, such as request timeouts or running out of memory, at which point it may be too late to completely recover.

Strong Thread Priorities and Inversion Control
In typical Java applications, application threads run at the same default priority. This means a system can't guarantee that all of the tasks will complete before the deadline, regardless of load, as there are a limited amount of CPU cycles to go around.

To address this issue, RTSJ defines at least 28 priority levels and requires that they are strictly enforced. RTSJ implementations require a real-time–capable operating system to support multiple priorities and to enable high-priority threads to preempt low-priority threads.

Priority inversion is a problem in real-time systems where a low-priority thread shares a resource with a high-priority thread, such as a file handle. For example, if a low-priority thread holds a resource and a high-priority thread is ready to run, then the low-priority thread can block the high-priority thread and act as if it is higher priority — hence, the term "inversion."

To solve this problem, Sun's Java Real-Time System (Java RTS) uses a technique called "priority inheritance protocol" in its scheduler. This feature avoids priority inversion by boosting the priority of a thread, putting a lock of highest-priority on that thread and waiting for that lock to clear, ensuring that the priority runs without interruption from other threads.

Threads and Scheduling
The RTSJ allows non-real-time, soft-real-time and hard-real-time activities to coexist within a single Java application. In addition to the standard java.lang.Thread (JLT) thread type, RTSJ defines RealtimeThread (RTT) and NoHeapRealtimeThread (NHRT) real-time thread types, which are selected depending on the temporal needs of a given thread or activity.

JLT threads are usually supported for non-real-time activities and used for reuse of existing java code. JLT threads use the 10 priority levels specified by the thread class, but these priority levels are not suitable for real-time activities because they do not provide temporal execution guarantees.

RTT threads are intended for soft-real-time activities. They take advantage of the stronger thread-priority support in RTSJ and are scheduled on a "run-to-block" basis, rather than a "time-slicing" basis. The scheduler can preempt a real-time thread if a real-time thread of higher priority becomes ready-to-run. RTT threads can also make use of a real-time garbage collector in RTSJ, which is designed specifically to minimize pauses for high-priority events. When the garbage collector runs, it creates a pause for all threads on the system. The time at which this pause will occur is fairly unpredictable, and it can cause failure — or worse — for the embedded application.

NHRT threads are intended for hard-real-time activities. To maximize predictability, NHRT threads cannot allocate memory from the heap of collected garbage. Allocating memory from the heap may trigger the garbage collector to run again and that can cause a task to miss its deadline. Instead, NHRT threads can use scoped and immortal memory features to allocate memory on a more predictable basis. NHRT threads are typically reserved for the highest-priority tasks, and if desired, they can interrupt the garbage collector.

Memory Regions and Management
Just like standard virtual machines, Real-Time Java virtual machines maintain a heap of collected garbage that can be used by both JLT and RTT threads. However, using memory from the standard heap subjects threads to GC-based pauses. To resolve this issue, the RTSJ also specifies two new memory regions called "immortal memory" and "scoped memory" (see Figure 1).

Immortal memory is an area of memory where garbage is not collected; once an object is allocated from immortal memory, the memory used by that object cannot be reclaimed. Managing immortal memory requires greater care than managing memory allocated from the standard heap, because if immortal objects are leaked by the application, they cannot be reclaimed and, eventually, all of the memory is consumed. Typically, immortal memory is used for static, shared objects.

Scoped memory is only available to RTT and NHRT threads. Its regions are intended for objects with a known lifetime, such as temporary objects created during a given transaction. Similar to immortal memory, scoped memory is an area of memory where garbage is not collected. However, the entire region of scoped memory is reclaimed through a pointer reset at the end of its lifetime (for example, when the task finishes).

Useful Abstractions
Finally, the RTSJ outlines several high-level abstractions to help you change the dynamic of programming a real-time system. Typically, a real-time developer is focused on time; you use timers to set and watch, and several checks to ensure the critical code executes before its deadline. The RTSJ attempts to point your focus to the tasks at hand and to require that the virtual machine takes care of time-based bookkeeping.

You've already learned about some RTSJ features for dealing with time-based bookkeeping, such as deadline-miss handlers. But one of the most exciting is the waitForNextPeriod() function. This function allows you to set up a long-running loop, perform some tasks, and then return control to the scheduler, which takes care of re-invoking the function when the specified period arrives.

Here is some pseudo code to illustrate how you can easily create a real-time application:

while (not_done) {
   pend = getPendulumAngle();
   cart = getCartPosition();
   new_cart = controlAlgorithm(pend,cart); // calculate move
   setMotor(new_cart);  // tell cart to reposition itself
   waitForNextPeriod();
}

In this example, the code balances an inverted pendulum using a motorized cart. The period of time for the thread is set to 5 milliseconds.

The convergence of faster, cheaper processors and the increasing availability of Java software on various standard platforms mean that you can more easily take advantage of Java software's inherent ease-of-use, multi-platform support and security in your embedded development. The RTSJ's support for real-time activities enables you to reuse your code and implement Real-Time Java's high-level abstractions. Real-Time Java lets you develop predictable Java code faster and more simply.