Unerlässlich für viele in C geschriebene Treiber sind Interrupt Handler. Sie sind ebenso mächtig wie schwer zu programmieren, denn der kleinste Fehler darin bringt das gesamte System zum Absturz. Gleichzeitig ist das Debugging darin erschwert, denn selbst ein einfaches printf ist nicht erlaubt. Auch muss sichergestellt werden, dass der Handler abgearbeitet wurde, ehe der Interrupt ein weiteres Mal ausgelöst wird.
Das Gegenstück in Java, ein Async-EventHandler, ist ungleich einfacher zu programmieren: Innerhalb des Handlers darf beliebiger Java-Code ausgeführt werden. Wird das externe Ereignis, das sog. Happening (also beispielsweise ein Interrupt), während der Bearbeitung ein weiteres Mal ausgelöst, so kann der Handler das zweite Auftreten bearbeiten, wenn er mit dem ersten fertig ist. Zum Debuggen kann ein beliebiger Java-Debugger, beispielsweise der in Eclipse enthaltene, mit der Java-VM verbunden und wie für jedes andere Java-Programm verwendet werden.
Verglichen mit einem C-Interrupt Handler ist ein AsyncEventHandler ein Schwergewicht: Für jedes zu behandelnde Ereignis muss zunächst ein Thread-Wechsel durchgeführt werden, was je nach Betriebssystem relativ „teuer“ ist. Für Ereignisse, die nicht häufiger als einige 1000 Mal pro Sekunde auftreten und deren Behandlung relativ komplex ist, würde man in C vermutlich von Hand einen ähnlichen Ansatz verfolgen und die eigentliche Behandlung in einen eigenen Thread auslagern – und dabei den gleichen Overhead in Kauf nehmen. Für Ereignisse, die häufiger auftreten können oder deren Behandlung nicht viel mehr als das Inkrementieren eines Zählers in Anspruch nimmt, ist ein C-Interrupt-Handler möglicherweise ausreichend.
AsyncEventHandler sind also eine sichere, einfache Möglichkeit, auf ein externes Ereignis zu reagieren. Was die Plattformunabhängigkeit betrifft, bezeichnet die RTSJ mit dem Begriff „Happening“ ein Ereignis außerhalb der Java-VM. Ein Happening kann beispielsweise ein Prozessorinterrupt oder ein POSIX-Signal sein. Das mit einem Happening assoziierte Java-Objekt heißt „Event“. Happenings sind jedoch sehr systemabhängig. Fast alle Betriebssysteme, die in Embedded-Systemen eine Rolle spielen, unterstützen POSIX-Signale. Auf diesen Systemen können mit Hilfe von RTSJ POSIXSignale von einem AsyncEventHandler behandelt werden. Welche CPU-Interrupts zur Verfügung stehen, unterscheidet sich mit jedem Prozessortyp. Wie diese von der Java-VM bezeichnet werden, hängt außerdem von der VM ab, denn RTSJ schreibt hier keine feste Namensgebung vor. In der Regel zeigen sich RTSJ-Anbieter jedoch kooperativ und sind gerne bereit, die VM um neue Happenings zu erweitern.
In RTSJ existieren folgende Speicherbereiche:
Zur Treiberprogrammierung in Java ist RawMemoryAccess nahezu unverzichtbar. Wie das Beispiel in Listing 3 zeigt, ist der Zugriff auf ein RawMemoryAccess-Objekt weitgehend plattformunabhängig. Dies gilt jedoch nicht für das Erzeugen des Objektes. In der Regel wird direkt die Startadresse verwendet, um den Speicher eines Gerätes zu bezeichnen. Diese wird sich bei einem Hardware-Wechsel vermutlich ändern. Wenn man akzeptiert, dass derart hardwarenahe Zugriffe völlig hardwareunabhängig nicht möglich sind, weiß man RawMemoryAccess bald zu schätzen. Er bietet eine sichere und zuverlässige Möglichkeit, in Java-Gerätespeicher zu adressieren. Aufgrund der Java-Sicherheitsmechanismen muss der Java-VM beim Start mitgeteilt werden, dass Zugriff auf diese Speicherregion erlaubt ist. Auch mit RTSJ können Java-Programme also nicht völlig nach Belieben Speicher auslesen oder manipulieren.
Da die Zugriffssicherheit mit Performance-Einbußen bezahlt werden muss, ist sorgfältig abzuwägen, für welche Aufgaben RawMemoryAccess geeignet ist. Ein Grafiktreiber, der hohen Datendurchsatz erfordert, gehört sicherlich nicht dazu. Auch wird wohl niemand einen vom Hersteller ausgelieferten, in C geschriebenen Treiber mit Hilfe von RTSJ neu implementieren wollen. Für solche Fälle gibt es das Java Native Interface JNI. Anders sieht es aus, wenn ein komplexer Zugriff auf spezielle, eigenentwickelte Geräte erforderlich ist, für die noch kein Treiber existiert. Die verringerte Zeit zum Debuggen und zur Fehlersuche sowie die geringere Zahl der Fehler, die beim Kunden auftreten, gleichen den Performance-Unterschied wieder aus.
Echtzeit-Threads funktionieren auf allen Prozessorarchitekturen und Betriebssystemen gleichermaßen. Lediglich die Auflösung der Uhr ist dabei systemspezifisch. Berücksichtigt werden sollte außerdem, dass Misshandler in RTSJ optional sind. Nicht alle RTSJVMs unterstützen ihre Verwendung.
In Kombination mit einer echtzeitfähigen Speicherverwaltung ist die RTSJ noch einfacher zu benutzen. Somit entfällt beispielsweise die Einschränkung, dass NoHeapRealtime-Threads nur auf bestimmte Speicherbereiche zugreifen dürfen. Allerdings werden Programme, die sich darauf verlassen, nur noch auf Java Virtual Machines mit echtzeitfähiger Garbage Collection funktionieren.
Bei JamaicaVM wird Garbage Collection beispielsweise nicht in einem eigenen Thread, sondern stets dann ausgeführt, wenn ein neues Objekt erzeugt wird. Zu diesem Zeitpunkt weiß der Entwickler, dass jede Java-VM eine bestimmte Zeit braucht, um den Speicher zu initialisieren. Er wird daher an zeitkritischen Stellen keine sehr großen Objekte allozieren. Der Thread, in dem ein Objekt erzeugt wurde, bezahlt für die Arbeit, die er dem Garbage Collector früher oder später verursachen wird, indem er ein wenig seiner eigenen Rechenzeit abgibt. Da der Garbage Collector mit der Priorität des allozierenden Threads läuft, kann ein höher priorisierter Thread jederzeit den Vorgang unterbrechen. Bild 3 veranschaulicht diese Vorgehensweise.
In RTSJ existieren folgende Speicherbereiche:
In RTSJ sind Zeiten daher immer fest mit einer Uhr verknüpft. Beim Versuch, die Zeiten unterschiedlicher Uhren zu vergleichen, wird eine Exception ausgegeben.
Zur Verwendung weiterer Uhren neben der Systemuhr ist ein Treiber erforderlich. Dank RTSJ kann dieser selbst in Java geschrieben werden, Anpassungen beim Wechsel auf eine andere Hardware sind hier aber sicher zu erwarten: Wie wird die Uhr initialisiert? Aus welcher Speicheradresse kann die Uhrzeit gelesen werden?
Die interne Darstellung von Uhrzeiten erfolgt mit einer Präzision von 84 bit. Somit kann der Zeitraum von 292 Millionen Jahren mit der Auflösung von einer Nanosekunde dargestellt werden. Zur komfortablen Benutzung und Darstellung können die Klassen Date und Calendar (Listing 2) aus den Java-Standard-Klassen verwendet werden.
Das Thread-Modell von Standard-Java ist für zeitkritische Aufgaben nicht verwendbar. Der Grund liegt in der Speicherbereinigung (Garbage Collection), die in unvorhersehbaren Zeitabständen das System komplett anhält, wie Bild 1 illustriert. Aus diesem Grund hat die RTSJ eine neue Thread-Art hinzugefügt: NoHeapRealtime-Threads. Diese haben höhere Prioriät als der Garbage Collector und können daher nicht unterbrochen werden. Wie der Name bereits vermuten lässt, kann aus NoHeapRealtime-Threads nicht auf Objekte zugegriffen werden, die im normalen Java-Heap liegen. Bild 2 illustriert das neue Thread-Modell.
Besonders hilfreich für Embedded-Systeme sind die Release-Parameter der NoHeap RealtimeThreads. Kann ein periodischer Thread nicht in der dort spezifizierten Zeit seine Aufgabe durchführen, so wird ein „Deadline Misshandler“ aufgerufen. Eine Software, die einen Film abspielt, kann dies beispielsweise verwenden, um mit der Berechnung des nächsten Bildes fortzufahren, wenn ein Bild nicht rechtzeitig angezeigt werden kann. In einem System, das harte Echtzeit erwartet, kann der Deadline Misshandler verwendet werden, um einen Alarm auszulösen.