Skip to main content

Real-time Java, Part 6: Simplifying real-time Java development

Introducing the Lifecycle Memory Managed Periodic Worker Threads pattern

Michael Dawson (michael_dawson@ca.ibm.com), Advisory Software Developer, IBM Ottawa Lab
Michael Dawson
Michael Dawson graduated in 1989 from the University of Waterloo with a bachelor's degree in computer engineering and in 1991 from Queens University with a master's degree in electrical engineering, specializing in cryptography. He then did security consulting work and developed EDI security products. Next, he was the development lead for a start-up that delivered security products across various platforms. He has since held leadership roles in teams developing e-commerce applications and delivering them as services including EDI communication services, credit card processing, on-line auctions, and electronic invoicing. The technologies used ranged from C/C++ to Java and J2EE platforms and components across a range of operating systems. In 2006, Michael joined IBM and works on the J9 JVM and WebSphere Real Time.

Summary:  Now that real-time Java™ virtual machines support scoped memory, defining common patterns for scoped memory usage can improve developer productivity. These patterns reduce the need to understand or work with scopes directly by providing scopes' core functions with less complexity. This article, the sixth and last in the Real-time Java series, introduces the Lifecycle Memory Managed Periodic Worker Threads pattern as a model for simplifying real-time Java development. It demonstrates the pattern's feasibility through a sample implementation and simple example application.

View more content in this series

Date:  03 Jul 2007
Level:  Intermediate
Activity:  3385 views

The Real-time Specification for Java (RTSJ) provides extensions to facilitate the development of real-time (RT) applications using the Java language. One such extension is a mechanism for applications to manage areas of memory outside of the heap and avoid delays that are caused by garbage collection (GC). These memory areas are referred to as scoped and immortal memory. To ensure the integrity of the heap and the immortal memory area, the RTSJ defines rules for using scoped memory. These rules control both the relationships allowed between scopes and the allowed references from an object in a scope to objects in other memory areas. The rules enable scopes to be used in a wide range of design patterns and applications, but such flexibility comes at the cost of complexity. As a result, some developers find using scopes difficult and vulnerable to mistakes. This article introduces a technique that you can use to simplify scoped memory usage.

The Lifecycle Memory Managed Periodic Worker Threads pattern

A common requirement in an RT system is a set of threads that execute periodically and cooperate to complete some tasks. The RTSJ includes specific mechanisms for running threads in a periodic fashion. The Lifecycle Memory Managed Periodic Worker Threads (LMMPWT) pattern aims to provide a simplified memory-management model that's suitable for a range of use cases where periodic threads must not be interrupted by GC.

LMMPWT pattern requirements

To be applicable to real-world applications, the LMMPWT pattern must satisfy at least the following requirements:

  • Accommodate one or more threads working together to accomplish a task.
  • Provide execution uninterrupted by GC.
  • Provide a mechanism for cooperating threads to communicate.
  • Provide life-cycle management for objects.

Memory-management model

The LMMPWT pattern constrains the available object lifetimes to those most useful for a periodic thread, while minimizing how long objects live and how much total memory is required. The supported object lifetimes are:

  • Retain Forever: Objects with this lifetime are never garbage collected and are accessible to all threads.

  • Retain Thread Group: Objects with this lifetime are available while any one of the threads in a group of cooperating threads has not terminated. These objects are accessible only by threads within the group of threads.

  • Retain Thread: Objects with this lifetime are available only while a particular thread is running and are accessible only to that thread.

  • Retain Iteration: Objects with this lifetime are available only while a thread is executing a particular iteration of its task. These objects are accessible to that thread only during the iteration they are created in.

This memory-management model fits with the life cycle of a typical set of periodic threads cooperating to complete a set of tasks. The logic for each thread follows the model of execute>wait for next period>execute>wait for next period, and so on. During each execution (or, as I'll refer to it now, iteration), the thread can allocate a number of objects. Some objects are needed only during that iteration itself, some are needed in subsequent iterations, some may need to be shared with other threads in the group of cooperating threads, and some may need to be shared outside this group. The model supports these requirements by allowing the developer to create objects with the appropriate lifetime.

This model simplifies application design. Instead of needing to consider which scopes are required, you simply decide how long the object must be retained. This limits the granularity of memory management, but in most cases the simplicity, maintainability, and ease of development outweigh the advantages of finer control. It is expected that the majority of the objects needed by applications will be allocated with a lifetime of Retain Iteration, indicating they are freed at the conclusion of each iteration.

To maintain the integrity of objects with different lifetimes, you must follow one rule when referencing objects: an object may not reference another object with a shorter lifetime. This is consistent with the rules for scopes defined within the RTSJ, and the enforcement can be carried out by the underlying scope implementation. The rule is summarized in Table 1:


Table 1. Rule for referencing objects with different lifetimes
Lifetime of object containing referenceReference permitted to object with Retain Forever?Reference permitted to object with Retain Thread Group?Reference permitted to object with Retain Thread?Reference permitted to object with Retain Iteration?
Retain ForeverYesNoNoNo
Retain Thread GroupYes

Yes

No

No

Retain Thread

Yes

Yes

Yes

No

Retain Iteration

Yes

Yes

Yes

Yes

A key element of any memory-management model is controlling when objects are collected and memory is returned to the system. Table 2 outlines this aspect of the model for each of the supported object lifetimes:


Table 2. When objects are collected and memory returned for each object lifetime
Object lifetimeWhen objects are collected and memory is recovered

Retain Forever

Never.

Retain Thread Group

When all of the threads in a group of threads have terminated, all objects with this lifetime are collected, and the memory allocated for that group is returned to the system.

Retain Thread

When a thread terminates, all of the objects with this lifetime are collected, and the memory allocated for that thread is returned to the system.

Retain Iteration

When the current iteration for a thread terminates, all the objects with this lifetime are collected. The memory area remains committed to the thread for the next iteration. When the thread finishes its last iteration and terminates, the memory allocated for that thread is returned to the system.

Managing objects

You must be able to set the lifetimes for allocated objects. To minimize the work required on a per-object basis, the default should minimize the objects for which you must specify the object lifetime. Retain Iteration seems to be the best fit for the default, because you should retain objects beyond an iteration only if the application requires it. Using Retain Iteration lets you ignore temporary objects that the application creates and that are created by methods that it calls. For objects that you must retain, you need to identify and understand the requirement for these objects anyway, so specifying their lifetimes will be a small increment to the work already required.

In the general case, you create an object using:

Object ref = new Object();

Ideally, you should be able to use the same code with some additional markup, as in this example:

/*[Lifetime=Retain Thread]*/
Object ref = new Object();

The best mechanism may depend on your environment and could be implemented using, for example, preprocessors, annotations, or helper objects. The model does not depend on the mechanism selected; however, the mechanism should allow you to specify the lifetime without requiring you to understand scopes and should avoid cluttering the code to avoid affecting maintainability. I'll discuss one possible approach in this article's Implementation using scopes section.

You also need to be able to store references to objects of different lifetimes. In particular, you will want to be able to create objects with a Retain Thread or Retain Thread Group lifetime in one iteration and access them in a subsequent iteration. The rules for references between objects of different lifetimes may prohibit member variables in the thread object itself from containing references to these objects. The model needs to include a mechanism for passing root objects to each iteration that can be used to store and retrieve objects that have a lifetime of Retain Thread Group or Retain Thread.


Implementation using scopes

You can implement the proposed model using standard RTSJ scopes and the immortal memory area. Scopes are used to support objects with the defined lifetimes, as indicated in Table 3:


Table 3. Memory area used for each object lifetime
Object lifetimeMemory area used

Retain Forever

Immortal.

Retain Thread Group

Top-level scope allocated when a group of threads is started.

Retain Thread

Scope allocated for thread when thread is started and that remains on the threads scope stack until it terminates.

Retain Iteration

Scope allocated for thread when thread is allocated. Scope is entered and exited for each iteration so that it is cleared between iterations.

For a thread running an iteration, the scope stack would be as shown in Figure 1:


Figure 1. Scope stack
ScopeStack picture

Sample implementation

The following sample API classes demonstrate the use of the proposed model using this scope-based implementation:

  • PeriodicThread
  • PeriodicThreadGroup
  • ObjectRootsHelper
  • PeriodicThreadDescriptor
  • StartPeriodicThreads

PeriodicThread is the base class from which worker threads in the set of cooperating threads must be derived. It manages the scopes behind the scenes for you. It defines the following constants, which are used to specify object lifetimes:

  • RETAIN_FOREVER
  • RETAIN_THREAD_GROUP
  • RETAIN_THREAD
  • RETAIN_ITERATION

The run method on this class is final so that derived classes cannot use it to specify their logic. Instead, you should override the following method so that it contains the logic for the thread:

threadLogic(ObjectRootsHelper helper)

This method is called each time the thread should execute the logic for an iteration.

An instance of ObjectRootsHelper is passed in to help the thread manage objects whose lifetimes exceed the iteration. Because the thread itself may be allocated out of a scope (in this case the scope for the Thread Group), it may not be possible to use it to store references to objects with certain lifetimes. ObjectRootsHelper provides the following methods, which you can use to store and retrieve references to these objects:

  • void add(int lifetime, String key,Object obj)
  • void addSynchronized(int lifetime,String key, Object obj)
  • Object get(int lifetime, String key);
  • Object getSynchronized(lifetime,String key);

ObjectRootsHelper supports managing objects with lifetimes of RETAIN_THREAD_GROUP and RETAIN_THREAD.

Because the logic in threadLogic is invoked for each iteration, the thread needs a mechanism to indicate that it has completed its last iteration and should terminate. The setTerminate() method is provided so that the thread can tell the framework when it wants to terminate.

PeriodicThread also includes the following method:

public Object allocate(final Class classObject, 
                       int lifespan, 
                       final Object ... parms)
   throws IllegalArgumentException

The allocate() method lets you allocate an object with a given lifetime. For example, when no parameters are required:

token = (WatchedThreadToken) allocate(WatchedThreadToken.class,
                                      PeriodicThread.RETAIN_THREAD_GROUP);

Or when parameters are required:

watchedThreads = (WatchedThreads) allocate(WatchedThreads.class,
                                           PeriodicThread.RETAIN_THREAD,
                                           new PrimInt(10),
                                           new PrimInt(3));

Note the way primitives are passed in. Variable-length argument lists require you to pass com.ibm.realtime.easyrtj.prmitive.PrimXXX instead of the primitive itself when the constructor for the class includes primitive parameters (long, int, float, double, short).

If an object created by a thread needs to do allocation internally, you must pass a reference to the thread itself to the object so that the allocate() methods can be called when needed.

A single constructor is provided for PeriodicThread:

PeriodicThread(SchedulingParameters scheduling, 
               RelativeTime period,
               Long threadMemorySize,
               Long iterationMemorySize,
               PeriodicThreadGroup group)

This constructor is public because PeriodicThread must be subclassed. However, you're not expected to call the constructor directly. The PeriodicThreadDescriptor and StartPeriodicThreads helper classes are provided so that you don't need to construct PeriodicThreads directly.

The PeriodicThreadDescriptor class describes a thread to be started as part of a group. It provides a single public constructor:

PeriodicThreadDescriptor(int priority, 
                         Class classObject, 
                         long threadMemorySize, 
                         long iterationMemorySize, 
                         long period) 

This constructor lets you specify the priority, memory sizes, and period for a thread, along with the class that represents the thread to be run (a class that extends PeriodicThread).

You build an array of PeriodicThreadDescriptors and pass them to StartPeriodicThreads' static startThreads method along with the size of the memory to be shared between the threads. startThreads starts the threads as defined by the descriptors such that they share a memory of the size indicated (that is, share objects with a lifetime of RETAIN_THREAD_GROUP).

In summary, to use the sample classes you:

  1. Create one or more classes that subclass PeriodicThread, putting the required logic in the threadLogic method.
  2. Create a PeriodicThreadDescriptor for each thread to be run as a group.
  3. Start the threads by calling startThreads, passing in the array of descriptors.

Sample watchdog code

A typical RT use case is to have a group of threads cooperating to carry out a set of tasks and to use a watchdog to ensure that the threads continue to be live and carry out their tasks. Each thread must periodically notify the watchdog to indicate that it is still alive. A monitoring thread raises an alarm if any thread doesn't kick within a given time interval.

The sample watchdog code (see Download) defines WatchedPeriodicThread as a subclass of PeriodicThread. This class's threadLogic method is final, and subclasses put the logic to be run in the watchedLogic method. The ObjectRootsHelper passed into threadLogic is passed on to the watchedLogic method. Also, subclasses must implement getThreadName to return the unique name to be associated with the thread.

In the threadLogic for WatchedPeriodicThread, code is added to create a WatchedThreadToken object with a lifespan of RETAIN_THREAD_GROUP. It is then shared with other threads by storing it with ObjectRootsHelper, with the name of the thread as the key. Each time threadLogic is run, it runs the watchedLogic method provided by the subclass and then calls kick on its WatchedThreadToken to indicate the thread is still alive.

WatchDogThread allocates a WatchedThreads object with a lifespan of RETAIN_THREAD and uses it to track the liveness of the threads it is watching. Each time threadLogic is called for WatchDogThread, it validates that the threads it is watching are still live and raises an alarm if any thread has died. (It simply uses printf, but this could easily send an SNMP trap, for example.)

Thread1 is a periodic thread (extends WatchedPeriodicThread) that runs for 10 iterations and then dies because of an "unexpected" exception. Thread2 is a simple periodic thread (extends WatchedPeriodicThread) that continues to run forever.

The TestMain class uses StartPeriodicThreads.startThreads to start Thread1, Thread2, and the WatchDogThread. Running the test program shows that WatchDogThread starts to monitor Thread1 and Thread2 and then raises an alarm when Thread1 dies.

This sample demonstrates the use of the model with objects that have RETAIN_THREAD_GROUP, RETAIN_THREAD, and RETAIN_INTERATION (the default) lifespans.

The download provides a JAR file that includes the source and compiled classes for the sample implementation and the watchdog application. To run the sample, use the following command line with an RT virtual machine:

java -cp LMMPWTSample.jar com.ibm.realtime.easyrtj.sample.TestMain


Conclusion

Alternative approaches, such as RT garbage collectors, reduce the need to use scopes, but it's likely that scopes will still be necessary in some applications. This article introduced the LMMPWT pattern for simplifying scoped memory usage. To demonstrate the pattern's feasibility and usefulness, it outlined a sample implementation using scopes and provided a sample application showing the model's use with objects of the various lifetimes.



Download

DescriptionNameSizeDownload method
Sample source codej-rtj6.jar85KB HTTP

Information about download methods


Resources

Learn

Get products and technologies

  • WebSphere Real Time: WebSphere Real Time lets applications dependent on a precise response times take advantage of standard Java technology without sacrificing determinism.

  • Real-time Java technology: Visit this IBM alphaWorks® research site to find cutting-edge technologies for RT Java.

Discuss

About the author

Michael Dawson

Michael Dawson graduated in 1989 from the University of Waterloo with a bachelor's degree in computer engineering and in 1991 from Queens University with a master's degree in electrical engineering, specializing in cryptography. He then did security consulting work and developed EDI security products. Next, he was the development lead for a start-up that delivered security products across various platforms. He has since held leadership roles in teams developing e-commerce applications and delivering them as services including EDI communication services, credit card processing, on-line auctions, and electronic invoicing. The technologies used ranged from C/C++ to Java and J2EE platforms and components across a range of operating systems. In 2006, Michael joined IBM and works on the J9 JVM and WebSphere Real Time.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=240611
ArticleTitle=Real-time Java, Part 6: Simplifying real-time Java development
publish-date=07032007
author1-email=michael_dawson@ca.ibm.com
author1-email-cc=jaloi@us.ibm.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers