Speicherbereinigung ("Garbage CoUection) für Smart Cards
Die Erfindung betrifft ein Verfahren zur automatischen Speicherbereinigung (Garbage CoUection), insbesondere einen Tracing Collector, und ein dabei verwendetes Verfahren zum Durchsuchen von dynamisch zugewiesenem Heap-Speicher nach referenzierten oder nicht referenzierten Objekten, insbesondere für Rechnersysteme mit geringen Systemressourcen (Datenträger), wie z.B. Smart Cards (Chipkarten), sowie einen entsprechenden Datenträger.
Ein Datenträger, insbesondere Smart Card, insbesondere Java Card, hat typischerweise einen Mikroprozessor, mehrere Speicher, z.B. ROM, EEPROM und RAM, wobei ROM und/oder EEPROM teilweise oder ganz durch Flash- Speicher ersetzt sein können, und ein oder mehrere Schnittstellen.
Ein auf einem Rechnersystem ablaufendes Programm (Computerprogramm) weist sich Arbeitsspeicher zu, d.h. allokiert Arbeitsspeicher, in welchem die einzelnen Programmcode-Befehle des Programms ausgeführt werden.
Der Arbeitsspeicher lässt sich in einen statisch zugewiesenen Speicher und einen dynamisch zugewiesenen Heap-Speicher unterteilen. Der statisch zugewiesene Speicher wird vor dem Ablaufen des Programms zugewiesen, z.B. während des Compi- lierens oder Linkens des Programms, wohingegen der dynamisch zugewiesene Heap- Speicher während der Laufzeit des Programms zugewiesen wird, d.h. während der Programmcode abläuft.
Zum statisch zugewiesenen Speicher zählt - zumindest teilweise - der Stack- Speicher, auf dem z.B. Variablen beim Aufruf von Methoden (z.B. Algorithmen) angelegt werden. Solche Methoden können beispielsweise Methoden sein, die eine automatische Speicherbereinigung zum Gegenstand haben (siehe weiter unten).
Bei objektorientierten Programmiersprachen wie z.B. Java™ von Sun Microsystems wird die dynamische Zuweisung von Arbeitsspeicher während der Laufzeit des Pro-
gramms in der Form durchgeführt, dass im Heap-Speicher (bei Java Cards i.d.R. im EEPROM oder ggf. Flash-Speicher) Objekte angelegt werden.
Jedes Objekt kann ein oder mehrere Referenzen auf andere Objekte enthalten, die wiederum Referenzen auf andere Objekte haben können. Durch die Referenzen von Objekten auf andere Objekte haben die Objekte im Heap-Speicher untereinander eine baumartige Organisationsstruktur (Baumstruktur).
Eine solche baumartige Organisationsstruktur, die auch Heap-Struktur genannt wird, ist in Fig. 1 gezeigt. An der Wurzel (Root) oder mit anderen Worten an der nullten Hierarchie-Ebene der Organisationsstruktur ist das Root-Objekt angeordnet, in Fig. 1 also das Objekt mit der Nummer eins (1). Bei anders gestalteten Heap-Strukturen kann es alternativ mehrere Root-Objekte geben, die dann auch als Start-Objekte bezeichnet werden, wobei von jedem Root-Objekt bzw. Start-Objekt aus eine eigene baumartige Organisationsstruktur ausgeht. Jede vom Root-Objekt ausgehende Referenz auf ein Objekt stellt den Eingang zu einem Zweig der Baumstruktur dar. In der Heap-Struktur von Fig. 1 sind ausgehend vom Root-Objekt 1 sechs Objekte 2, 20, 14, 26, 25 und 35 referenziert, so dass die Heap-Struktur sechs Zweige hat. Alle Objekte, die über die baumartige Organisationsstruktur direkt oder indirekt (d.h. über weitere Objekte) mit dem Root-Objekt verbunden sind (referenzierte Objekte genannt), sind vom Root-Objekt aus erreichbar. Nicht referenzierte Objekte, d.h. Objekte, die keine Verbindung über Referenzen zum Root-Objekt haben, sind vom Root-Objekt aus nicht erreichbar. In Fig. 1 sind z.B. die Objekte 31, 32, 33, 29, 30, 23 und 24 nicht referenziert und daher vom Root-Objekt aus nicht erreichbar.
Die Objekte, die vom Root-Objekt aus direkt referenziert sind, bilden die Objekte der ersten Hierarchie-Ebene. Die von Objekten der ersten Hierarchie-Ebene aus referenzierten Objekte bilden Objekte einer zweiten Hierarchie-Ebene usw.. Jeder Zweig der Baumstruktur hat eine Tiefe, die gleich der Anzahl Ebenen unter der nullten Hie- rarchie-Ebene (Root-Ebene) in diesem Zweig ist. In Fig. 1 ist z.B. die Tiefe des
Zweigs von Objekt 1 bis Objekt 5 gleich vier, die Tiefe des Zweigs von Objekt 1 bis
Objekt 7 ebenfalls gleich vier, die Tiefe des Zweigs von Objekt 1 bis Objekt 8 gleich drei etc..
Unter Bezugnahme auf Fig. 1 ist eine vorwärtsgerichtete Heap-Struktur derart orga- nisiert, dass zu jedem vorgegebenen Objekt (z.B. Objekt einer i-ten Hierarchieebene) jedes von dem vorgegebenen Objekt referenzierte Objekt (folglich Objekt der (i+1)- ten Hierarchieebene) einen größeren Index hat als das vorgegebene Objekt der i-ten Hierarchieebene. Unter Umständen kann es jedoch vorkommen, dass eine Heap- Struktur mit Rückwärtsreferenzen angelegt wird. Eine Rückwärtsreferenz ist dadurch ausgezeichnet, dass ein Objekt (einer i-ten Hierarchieebene) einen höheren Index hat als ein von diesem Objekt referenziertes Objekt (der darunterliegenden (i+l)-ten Hierarchieebene). In der Heap-Struktur aus Fig. 1 liegt beispielsweise zwischen dem Paar von Objekten (9, 6) eine Rückwärtsreferenz vor. Denn das Objekt Nr. 9 der zweiten Hierarchieebene hat einen höheren Index als das von Objekt Nr. 9 referen- zierte Objekt Nr. 6 der darunterliegenden dritten Hierarchieebene. Ebenso liegt zwischen den Objekt-Paaren ((i), (i+1)) = (9, 8), (40, 37), (40, 38), (38, 36), (35, 34) und (34, 19) von referenzierendem Objekt (i) und referenziertem Objekt (i+1) jeweils eine Rückwärtsreferenz vor.
Nicht mehr verwendeter Heap-Speicher (dynamisch zugewiesener Arbeitsspeicher) muss wieder freigegeben (deallokiert) werden, damit für neue Objekte wieder Heap- Speicher zur Verfügung steht. Bei objektorientierten Programmiersprachen kann und soll also insbesondere der durch nicht referenzierte Objekte belegte Speicherplatz freigegeben werden, da diese Objekte ohnehin nicht mehr erreichbar sind und somit "Garbage" (Müll) sind.
Eine Reihe von objektorientierten Programmiersprachen, beispielsweise Java, Smalltalk, Eiffel, Oberon, Perl, Python, Visual Basic und weitere, haben eine automatische Speicherbereinigung (Garbage CoUection), bei der nicht referenzierte Ob- jekte erkannt werden und der durch sie belegte dynamisch zugewiesene Heap- Speicher automatisch wieder freigegeben wird.
Die einfachste Form der automatischen Speicherbereinigung ist die Reference Coun- ting Garbage CoUection oder der Reference Counting Collector, bei dem jedes Objekt einen Referenzzähler enthält. Der Zählerstand des Referenzzählers gibt an, wie viele Referenzen auf das Objekt gerichtet sind. Für jede erzeugte Referenz auf das Objekt wird der Zählerstand um eins hochgezählt, und für jede gelöschte Referenz auf das Objekt wird der Zählerstand um eins heruntergezählt. Objekte, deren Referenzzähler einen Zählerstand von Null hat, werden als Garbage eingestuft und gelöscht bzw. der von den Garbage-Objekten besetzte Speicherplatz wird dem frei ver- fügbaren Heap-Speicher zugewiesen.
Neben Reference Counting Collectors gibt es die Tracing Collectors, bei denen Referenzen von der Root-Klasse (Wurzelklasse) des Heap- Speichers aus verfolgt ("getra- cet") werden.
Bei den Speicherbereinigungen (Garbage Collections) vom Typ Tracing Collector bildet der Mark-and-Sweep Collector den grundlegenden Algorithmus. Beim Mark- and-Sweep Collector wird zuerst, in der sogenannten Mark-Phase, der Heap-Speicher nach Referenzen durchsucht, wobei Objekte, auf die keine Referenzen gefunden wurden, identifiziert und markiert werden. Die Suche nach Referenzen beginnt dabei am Root-Objekt des Heap-Speichers und arbeitet sich von dort aus hierarchisch durch den gesamten Heap-Speicher durch. Die in der Mark-Phase markierten Objekte werden in der nachfolgenden Sweep-Phase gelöscht.
Bei zwei weiteren Formen von automatischer Speicherbereinigung (Garbage CoUection) vom Typ Tracing Collector, den Compacting Collectors und den Copying Collectors, wird der Heap-Speicher bei der Speicherbereinigung zusätzlich defragmentiert.
Sowohl Compacting Collectors als auch Copying Collectors beginnen mit einem
Durchsuchen des Heap-Speichers nach Referenzen, nur dass hier nicht die nicht refe-
renzierten "toten" Objekte gesucht werden, sondern die "lebenden" Objekte, auf die noch Referenzen gerichtet sind (referenzierte Objekte).
Bei Compacting Collectors werden die gefundenen referenzierten Objekte ("leben- den" Objekte) zu einem Ende des Heap-Speichers hin verschoben und dort kompak- tiert. Dadurch entsteht am entgegengesetzten Ende des Heap-Speichers ein großer zusammenhängender Speicherbereich. Anschließend werden alle Referenzen auf verschobene Objekte aktualisiert.
Bei Copying Collectors werden alle gefundenen referenzierten ("lebenden") Objekte von ihrem bisherigen alten Speicherbereich in einen neuen Speicherbereich kopiert und dabei lückenlos aneinandergefügt. Dadurch ist der neue Speicherbereich zusammenhängend, also defragmentiert. Der alte Speicherbereich wird als frei verfügbarer Speicherbereich deklariert.
Bislang sind Verfahren zur automatischen Speicherbereinigung - mit Ausnahmen - überwiegend auf ausgedehnte Rechnersysteme beschränkt. Bei Rechnersystemen mit geringen Systemressourcen wie z.B. Smart Cards reicht häufig der Arbeitspeicher für eine automatische Speicherbereinigung nicht aus.
Bei dem gängigen, in Fig. 1 veranschaulichten Verfahren zum Durchsuchen des Heap-Speichers nach referenzierten Objekten, das bei der Mark-and-Sweep Garbage CoUection verwendet wird, werden, ausgehend vom Root-Objekt, die einzelnen Zweige der Heap-Struktur nacheinander nach referenzierten Objekten durchsucht, wobei jedes aufgefundene referenzierte Objekt als referenziertes (und daher erreichbares) Objekt markiert wird. Gemäß Fig. 1 wird zum Durchsuchen und Markieren zuerst für das Root-Objekt ein entsprechender Such- und Markier- Algorithmus aufgerufen und das Root-Objekt (in Fig. 1 Objekt 1) markiert. Anschließend wird, nacheinander für jeden Zweig, der Such- und Markier- Algorithmus rekursiv für alle Hie- rarchie-Ebenen unterhalb der nullten Hierarchie-Ebene (Root-Ebene) aufgerufen, bis das Ende des Zweigs erreicht ist. In Fig. 1 wird z.B. für den ersten Zweig der Such-
und Markier- Algorithmus rekursiv aufgerufen für das Objekt 2 der ersten Hierarchie- Ebene, für das Objekt 3 der zweiten Hierarchie-Ebene, für das Objekt 4 der dritten Hierarchie-Ebene und schließlich für das Objekt 5 der vierten Hierarchie-Ebene. Anschließend wird, im Beispiel von Fig. 1, der Teilzweig durchsucht, der sich von Ob- jekt 2 bis Objekt 7 erstreckt, wobei der Algorithmus wieder rekursiv aufgerufen wird. Zu letzt wird, im Beispiel von Fig. 1, der Zweig durchsucht, der sich von Objekt 1 bis Objekt 19 erstreckt. Die maximale Tiefe des Baums aus Fig. 1 ist gleich fünf und ist in dem Zweig verwirklicht, der sich von Objekt 1 bis Objekt 36 erstreckt.
Im Beispiel aus Fig. 1 sind die Objekte 31, 32, 33, 29, 30, 23 und 24 nicht referenziert und daher „Müll" (garbage).
Der Speicherbedarf für einen rekursiven Algorithmus wie den obenstehend beschrie- benen, bei dem der Algorithmus innerhalb des Algorithmus immer wieder erneut aufgerufen wird, lässt sich nicht im Voraus ermitteln, wie nachfolgend dargelegt ist. Dies ist einer der Gründe, warum der herkömmliche rekursive Algorithmus in der Regel nicht ohne Weiteres auf ein Rechnersystem mit geringen Systemressourcen wie z.B. eine Smart Card übertragbar ist.
Jedes Objekt in einer Baumstruktur benötigt ein einzelnes Bit, also 1/8 Byte, für ein Flag, mit dem gekennzeichnet wird, ob das Objekt referenziert ist oder nicht. Eine Baumstruktur mit n Objekten benötigt somit n/8 Byte Speicher (statisch allozierten Arbeitsspeicher) für die Markierung der Objekte als referenziert oder nicht referen- ziert. Bei der Anwendung des Such- und Markier- Algorithmus auf einen Zweig der Baumstruktur wird für jeden Aufruf des Algorithmus und damit für jede Hierarchie- Ebene eine Anzahl von v Bytes für lokale Variablen verwendet. Für einen Zweig der Tiefe d' wird daher ein Anzahl von d' * v Bytes an Speicher für Variablen benötigt. Zu berücksichtigen ist hierbei, dass die Variablen eines bereits vollständig durch- suchten Zweigs wieder überschrieben werden können, wenn der nächste Zweig der Baumstruktur durchsucht wird, da die einzelnen Objekte des durchsuchten Zweig ja
bereits als referenziert oder nicht referenziert markiert sind. Relevant für den Speicherbedarf für Variablen ist somit die Tiefe des Zweigs der Baumstruktur mit der maximalen Tiefe d.
Sei also für eine Baumstruktur wie die in Fig. 1 dargestellte n := Anzahl der Objekte in der Baumstruktur d := maximale Tiefe der Baumstruktur v := Anzahl von Bytes, die bei dem Algorithmus für lokale Variablen ver- wendet werden (konstant pro Algorithmus- Aufruf) rc:= Speicherbedarf in Byte.
Dann gilt für den Speicherbedarf rc des Algorithmus in Byte, rc - n / 8 + d * v (1)
Der Speicherbedarf des rekursiven Such- und Markier- Algorithmus hängt also von der maximalen Tiefe der Baumstruktur ab, die noch unbekannt ist, solange die Baumstruktur nicht vollständig durchsucht worden ist. Folglich lässt sich der Spei- cherbedarf des herkömmlichen rekursiven Such- und Markier- Algorithmus, der z.B. bei der herkömmlichen Mark-and-Sweep Garbage CoUection verwendet wird, nicht im Voraus, vor dem Durchsuchen der Baumstruktur und damit vor der Laufzeit des Algorithmus, ermitteln.
Bei Rechnersystemen mit großzügig bemessenen Systemressourcen (Speicher und/ oder Rechenleistung) wie z.B. PCs, Workstations oder Servern wird einem rekursiven Such- und Markier- Algorithmus im Zweifelsfall überreichlich Arbeitsspeicher, insbesondere Stack-Speicher, zugewiesen.
Vor allem für Rechnersysteme mit geringen Systemressourcen, wie z.B. Smart
Cards, reicht der insgesamt verfügbare Speicher unter Umständen nicht aus, um im
Zweifelsfall überreichlich Arbeitsspeicher zuzuweisen. Daher ist es wünschenswert, den Speicherbedarf eines Algorithmus für die automatische Speicherbereinigung im Voraus zu kennen, um zu ermitteln, ob der Algorithmus auf dem Rechnersystem überhaupt lauffähig ist, d.h. ausgeführt werden kann, und um zu verhindern, dass der Arbeitsspeicher überläuft und in Folge beim Ausführen des Algorithmus Fehler im Programmablauf auf reten.
Ausgehend hiervon liegt der Erfindung die Aufgabe zu Grunde, ein Verfahren zur automatischen Speicherbereinigung (Garbage CoUection) und ein dabei verwendetes Verfahren zum Durchsuchen von dynamisch zugewiesenem Heap-Speicher nach referenzierten oder nicht referenzierten Objekten zu schaffen, bei dem der Speicherbedarf (Bedarf an Arbeitsspeicher) für die Durchführung der Speicherbereinigung im Voraus, vor der Ausführung des Verfahrens, ermittelbar ist.
Die Aufgabe wird gelöst durch ein Verfahren nach dem unabhängigen Verfahrensanspruch. Vorteilhafte Ausgestaltungen der Erfindung sind in den abhängigen Ansprüchen angegeben.
Bei dem erfindungsgemäßen Verfahren gemäß Anspruch 1 sind in dynamisch zuge- wiesenem Heap-Speicher Objekte angelegt. Nicht mehr erreichbare Objekte auf dem Heap-Speicher sollen mittels des erfindungsgemäßen Verfahrens gefunden werden, das hierzu den Heap-Speicher systematisch durchsucht. Gemäß dem unabhängigen Anspruch 1 wird das Durchsuchen des Heap-Speichers mittels eines Algorithmus durchgeführt, der ohne Rekursion arbeitet.
Dadurch, dass keine Rekursion durchgeführt wird, wird der Algorithmus nur ein einziges Mal aufgerufen, ohne dass er innerhalb des Algorithmus geschachtelt erneut aufgerufen wird. Die Anzahl v Bytes, die der Algorithmus für lokale Variablen benötigt, wird im Voraus nach Bedarf festgelegt und ist folglich bereits vor dem Aufru- fen des Algoritlimus bekannt. Da der Algorithmus nur ein einziges Mal aufgerufen wird, benötigt der Algorithmus für lokale Variablen insgesamt eine Anzahl von nur
genau ein Mal v Bytes (statt maximale Tiefe der Baumstruktur Mal v Bytes beim herkömmlichen rekursiven Verfahren). Daher ist bei dem erfindungsgemäßen Verfahren der Speicherbedarf zur Ausführung des Algorithmus im Voraus bekannt.
Daher ist gemäß Anspruch 1 ein Verfahren geschaffen, bei dem der Speicherbedarf (Bedarf an Arbeitsspeicher), insbesondere der Bedarf an statisch zugewiesenem oder zuzuweisendem Speicher (Arbeitsspeicher) für das Verfahren, im Voraus, vor der Ausführung des Verfahrens, ermittelbar ist, was speziell für Systeme mit begrenzten Speicherressourcen, wie z.B. Smart Cards, sehr vorteilhaft ist.
Mittels des Algorithmus gefundene referenzierte Objekte werden vorzugsweise markiert, damit in einem nachfolgenden Schritt die übrig gebliebenen nicht referenzierten Objekte gelöscht werden können, z.B. indem der von den nicht referenzierten Objekten belegte Speicherplatz als frei verfügbarer Heap-Speicherplatz deklariert wird. Das Löschen der nicht referenzierten Objekte bzw. Freigeben des durch sie belegten Speichers kann in einem Sweep-Schritt wie beim Mark-and-Sweep Verfahren erfolgen. Alternativ können die gefundenen referenzierten Objekte gerettet werden, wie beim Compacting Collector oder beim Copying Collector, und der übrig bleibende Speicher als frei verfügbarer Heap-Speicher deklariert werden. Wahlweise wird der Speicher bei der Speicherbereinigung zusätzlich defragmentiert.
Vorzugsweise ist jedem Objekt ein eindeutiger Objektindex zugewiesen, wobei durch die Objektindizes aller Objekte des Heap-Speichers eine eindeutige Reihenfolge der Objekte festgelegt ist. Gemäß dem Algorithmus werden die Objekte des Heap- Speichers in der Reihenfolge ihrer Objektindizes abgesucht, unabhängig von einer ggf. zu Grunde liegenden Heap-Struktur. Der Objektindex kann z.B. eine Zahl oder ein Alphabetbuchstabe oder eine lineare Adresse sein oder ein sonstiger Index, der mit weiteren Zahlen, Alphabetbuchstaben etc. eine eindeutige Reihenfolge bildet.
Vorzugsweise sind die Objekte in dem Heap-Speicher nummeriert und werden in der Reihenfolge ihrer Nummerierung abgesucht. Durch die Nummerierung ist jedem
Objekt ein eindeutiger Objektindex zugewiesen, mit dem das Objekt eindeutig adressierbar ist. Zusätzlich hat jeder Objektindex einen eindeutigen Nachfolger, so dass durch die Objektindizes aller Objekte der Heap-Struktur eine eindeutige Reihenfolge der Objekte festgelegt ist.
Beim Algorithmus mit Objekten mit Objektindex bzw. Nummerierung wird das Durchsuchen des Heap-Speichers streng nach dem Objektindex bzw. der Nummerierung der Objekte durchgeführt, unabhängig davon, ob ein Zweig der Heap-Struktur bis zum Ende durchsucht ist oder nicht. Beim üblicherweise verwendeten herkömm- liehen Algorithmus wird dagegen das Durchsuchen des Heap-Speichers in jedem
Zweig der Heap-Struktur, gemäß der der Heap-Speicher organisiert ist, bis zum Ende des jeweiligen Zweigs durchgeführt, bevor mit dem Durchsuchen des nächsten Zweiges begonnen wird, unabhängig von einer Nummerierung bzw. Indizierung der einzelnen Objekte.
Sofern der Heap-Speicher frei von Rückwärtsreferenzen ist - oder mit anderen Worten sofern der Heap-Speicher streng vorwärtsgerichtet ist - , genügt es, den Speicher ein einziges Mal in der Reihenfolge der Objektindizes bzw. der Nummerierung der Objekte zu durchsuchen, vorzugsweise beginnend beim als Start-Objekt verwendeten Root-Objekt, bei einer Ausführungsform wie der in Fig. 3 gezeigten z.B. mit der Nummer 1, bis zu einem letzten Objekt, gemäß Fig. 3 beispielsweise mit der Nummer 40. Falls das Root-Objekt die höchste Nummer hat, wird das Durchsuchen z.B. bei der höchsten Nummer begonnen und bis zum Objekt mit der niedrigsten Nummer (z.B. Eins oder Null) durchgeführt. Gefundene referenzierte Objekte werden in diesem Fall vorzugsweise markiert, so dass die nicht referenzierten Objekte übrig bleiben. Nachfolgend kann wie oben beschrieben ein Bereinigungsschritt folgen.
Alternativ wird ein Heap-Speicher, der bezüglich der Objektindizes bzw. Nummerierung vorwärtsgerichtet ist, gemäß dem Algorithmus genau zwei Mal durchsucht, wobei das zweite Mal des Durchsuchens zur Überprüfung durchgeführt wird, dass der Algoritlimus beendet werden kann.
Sofern der Heap-Speicher eine oder mehrere Rückwärtsreferenzen aufweist, d.h. falls der Heap-Speicher bezüglich der Objektindizes bzw. Nummerierung seiner Objekte mindestens ein Paar von referenzierendem Objekt (i) und referenzierten Objekt (i+1) aufweist, die zueinander eine Rückwärtsreferenz (rückwärtsgerichtete Referenz- Beziehung) haben, wird vorzugsweise der Heap-Speicher mindestens zwei Mal durchsucht.
Vorzugsweise wird der Heap-Speicher im Fall, dass er mindestens eine Rückwärtsre- ferenz hat, hintereinander mindestens ein Mal vorwärts und ein Mal rückwärts durchsucht.
Alternativ wird ein Heap-Speicher, der bezüglich der Objektindizes bzw. Nummerierung seiner Objekte mindestens ein Paar von referenzierendem Objekt ((i)) und refe- renzierten Objekt ((i+1)) aufweist, die zueinander eine Rückwärtsreferenz haben, mindestens drei Mal durchsucht, wobei das letzte Mal (z.B. das dritte Mal) des Durchsuchens zur Überprüfung durchgeführt wird, dass der Algorithmus beendet werden kann.
Jedes mittels des Algorithmus gefundene referenzierte aktuelle Objekt (aktuelles Objekt im Unterschied zu Objekten, die durch dieses aktuelle Objekt referenziert sind) wird vorzugsweise markiert. Dabei ist es weiter bevorzugt, dass das Verfahren beendet wird, sobald ein Durchlauf des Durchsuchens des gesamten Heap-Speichers durchgeführt worden ist, bei dem keine Markierung vorgenommen worden ist. Wahlweise wird nach einem Durchlauf, bei dem keine Markierung vorgenommen worden ist, ein nochmaliger Durchlauf des Durchsuchens als Prüfdurchlauf vorgenommen, um zu überprüfen, ob tatsächlich keine Markierungen mehr vorzunehmen sind. In beiden Fällen wird mit anderen Worten daran, dass ein Leerlauf durchgeführt worden ist, bei dem keine Markierungen vorgenommen worden sind, erkannt, dass das Verfahren beendet werden kann. Bei einem streng vorwärts gerichteten Heap- Speicher ist der nochmalige Durchlauf, d.h. der Prüfdurchlauf, vorzugsweise der
zweite Durchlauf. Bei einem Heap-Speicher mit mindestens einer Rückwärtsreferenz ist der nochmalige Durchlauf, d.h. der Prüfdurchlauf, beispielsweise der dritte Durchlauf des Durchsuchens des Heap-Speichers, allgemeiner der letzte Durchlauf. Dabei wird der Heap-Speicher je nach Bedarf mehrmals abwechselnd vorwärts und rückwärts durchsucht. Bei jedem Durchsuchen wird der Algorithmus nur ein einziges Mal aufgerufen, so dass der Speicherbedarf (Bedarf an statisch zugewiesenem Arbeitsspeicher, insbesondere Stack-Speicher) für den Algorithmus im Voraus ermittelbar ist. Bei jedem erneuten Durchlauf des Durchsuchens kann der Speicherplatz, den der Algorithmus beim vorangehenden Durchlauf belegt hat, wieder überschrie- ben werden. Daher erhöht sich der Speicherbedarf nicht mit der Anzahl von durchgeführten Durchläufen des Durchsuchens des Speichers. Insbesondere führen Rückwärtsreferenzen im Heap-Speicher, die ein mehrmaliges Durchsuchen des Heap- Speichers, abwechselnd vorwärts und rückwärts, erforderlich machen, nicht zu einer Erhöhung des Speicherbedarfs.
Weiter vorzugsweise wird das Markieren eines Objekts jeweils unter Verwendung eines in einem Speicher vorgesehenen Markierungsfeldes durchgeführt, das beispielsweise eine vorbestimmte Anzahl von Bits oder Bytes in einem Speicher benutzt, der für den Algorithmus zugänglich ist, z.B. im statisch zugewiesenem Stack- Speicher.
Gemäß einer bevorzugten Ausführungsform weist das Markierungsfeld für das aktuelle Objekt ein erstes Datenfeld und ein zweites Datenfeld auf, wobei das erste Datenfeld (valid) markiert wird, falls das aktuelle (gefundene) Objekt referenziert ist, und wobei das zweite Datenfeld (scanned) markiert wird, falls alle von dem aktuellen Objekt ausgehenden referenzierten Objekte dadurch als referenziert markiert sind, dass bei diesen referenzierten Objekten das erste Datenfeld (valid) markiert ist. Jedes Datenfeld kann z.B. als ein Flag ausgebildet sein. Das Flag wird zum Markieren des Objekts geschaltet, also je nach Implementierung gesetzt oder gelöscht.
Das Markierungsfeld gemäß der Erfindung ist vorzugsweise, anders als bei herkömmlichen Verfahren zum Durchsuchen eines Heap-Speichers, im RAM (Random Access Memory) vorgesehen. Bei herkömmlichen Verfahren zum Durchsuchen eines Heap-Speichers wird, um ein Objekt als referenziert zu markieren, ein Flag im Hea- der des Objekts gesetzt (geschaltet), wie durch Fig. 2 schematisch veranschaulicht ist. Da der Header des Objekts herkömmlicherweise in der Regel im EEPROM vorgesehen ist, ist auch das herkömmliche Flag im EEPROM vorgesehen. Hierdurch ist das herkömmliche Flag langsam und belastend für die Speicherressourcen eines Datenträgers (Smart Card, Chipmodul, Chip etc.), bei dem das Verfahren angewendet wird (EEPROM kann nur eine begrenzte Anzahl von Malen überschrieben werden). Das Verfahren mit dem bevorzugten erfindungsgemäßen Markierungsfeld im RAM hat daher den zusätzlichen Vorteil, dass es besonders schnell ist, da die Schreibzeit eines RAM deutlich niedriger ist als die eines EEPROM. Zudem ist das Verfahren mit dem bevorzugten erfindungsgemäßen Markierungsfeld im RAM besonders scho- nend für die Speicherressourcen eines Datenträgers, bei dem das Verfahren angewendet wird.
Weiter vorzugsweise werden mittels des Algorithmus gefundene Objekte, auf die keine oder noch keine Referenz gefunden wurde, nicht markiert. Der Algorithmus übergeht jedes solche Objekt einfach, ggf. bis er bei einem erneuten Such-Durchlauf erneut auf das Objekt trifft.
Das Verfahren wird vorzugsweise in einem Datenträger wie einer Smart Card, insbesondere einer Java Card, oder einem beliebig geformten Token verwendet oder in einem Chipmodul zum Einbau in einen Datenträger oder sonstigen Gegenstand bzw. in einem Chip zum Einbau in ein Chipmodul oder einen Datenträger etc. Der Datenträger bzw. das Chipmodul bzw. der Chip weist vorzugsweise einen Mikroprozessor und mehrere Speicher (ROM, EEPROM, RAM, ggf. Flash) auf. Der Algorithmus ist vorzugsweise im ROM, ggf. alternativ in einem Flash- Speicher, implementiert. Dem Algorithmus ist vorzugsweise ein Arbeitsspeicher, insbesondere ein Stack-Speicher im RAM des Datenträgers, statisch zugewiesenen, dessen erforderliche Größe sich
bei dem erfindungsgemäßen Verfahren vor der Laufzeit berechnen lässt. Das Markierungsfeld ist vorzugsweise im RAM implementiert, vorzugsweise im Stack-Speicher.
Im Folgenden wird die Erfindung anhand von Ausführungsbeispielen und unter Be- zugnahme auf die Zeichnung näher erläutert, in der zeigen:
Fig. 1 eine baumartig organisierte Heap-Struktur, anhand der die Mark-Phase eines Speicherbereinigungsverfahrens (Garbage CoUection) nach dem Stand der Technik veranschaulicht ist;
Fig. 2 eine schematische Darstellung eines im EEPROM einer Java Card abgespeicherten Objekt-Headers, in dem Referenzen auf ebenfalls im EEPROM abgespeicherte Daten abgespeichert sind, gemäß dem Stand der Technik;
Fig. 3 eine zu der in Fig. 1 gezeigten analoge baumartig organisierte Heap-Struktur, anhand der die Mark-Phase eines Speicherbereinigungsverfahrens (Garbage CoUection) gemäß einer Ausfuhrungsform der Erfindung veranschaulicht ist, zu einem ersten Verfahrensstand während des Durchsuchens des Heap- Speichers;
Fig. 4 ein Markierungsfeld mit zwei Datenfeldern „Valid" und „Scanned" für jedes Objekt der in Fig. 3 gezeigten Heap-Struktur, mit Markierungen, die dem in Fig. 3 gezeigten Verfahrensstand entsprechen;
Fig. 5 die Heap-Struktur aus Fig. 3 zu einem zweiten Verfahrensstand während des Durchsuchens des Heap-Speichers;
Fig. 6 das Markierungsfeld aus Fig. 4, mit Markierungen, die dem in Fig. 5 gezeigten Verfahrensstand entsprechen;
Fig. 7 die Heap-Struktur aus Fig. 3 zu einem dritten Verfahrensstand während des Durchsuchens des Heap-Speichers;
Fig. 8 das Markierungsfeld aus Fig. 4, mit Markierungen, die dem in Fig. 7 gezeig- ten Verfahrensstand entsprechen.
Fig. 3 zeigt eine baumartig organisierte Heap-Struktur, anhand der das Durchsuchen eines Heap-Speichers nach Referenzen, insbesondere bei der Mark-Phase eines Speicherbereinigungsverfahrens (Garbage CoUection), gemäß einer Ausführungsform der Erfindung veranschaulicht ist, zu einem ersten Verfahrensstand während des Durchsuchens des Heap-Speichers.
Der Heap-Speicher weist 40 Objekte auf, die fortlaufend von 1 bis 40 durchnumme- riert sind, wobei das Objekt mit der Nummer „1" das Root-Objekt ist. Für jedes Ob- jekt sind zwei Datenfelder vorgesehen, um das Objekt zu markieren, nämlich ein „valid"-Datenfeld und ein „scanned"-Datenfeld.
Beim Algorithmus gemäß der bevorzugten Ausführungsform, bei dem die Objekte in der Reihenfolge ihrer Nummerierung abgesucht werden, lässt sich der Speicherbe- darf vor Ablauf des Algorithmus ermitteln, wie nachfolgend gezeigt ist.
Sei also für eine Baumstruktur wie die in Fig. 3 dargestellte n := Anzahl der Objekte in der Baumstruktur v := Anzahl von Bytes, die bei dem Algorithmus für lokale Variablen verwendet werden (konstant pro Algorithmus- Aufruf) rc:= Speicherbedarf in Byte.
Dann gilt für den Speicherbedarf rc des bevorzugten erfindungsgemäßen Algorith- mus in Byte,
rc = (2 * n + 8) / 8 + v (2)
Dabei gibt der Term (2*n + 8) die Anzahl Bits (1/8 Bytes) an, die für die „valid"- und „scanned"-Datenfelder (valid-Flags und scanned-Flags) für die n Objekte der Heap-Struktur erforderlich sind. Der Term v gibt den Speicherbedarf in Bytes für lokale Variablen an und erscheint nur ein einziges Mal, da der Algorithmus pro Durchlauf des Durchsuchens des Heap-Speichers nur ein einziges Mal aufgerufen wird.
Aus Gleichung (2) lässt sich die Höchstzahl n
maχ von Objekten im Voraus ermitteln, die sich in einem Speicher der Größe rc unterbringen lassen:
Nachfolgend ist ein beispielhafter rekursionsfreier Algorithmus 1 angeführt, in dem das erfindungsgemäße Verfahren zum Durchsuchen eines Heap-Speichers und zum Markieren gefundener referenzierter Objekte implementiert ist. Sei n die Anzahl von Objekten im Heap-Speicher. Sei zudem eine Objektliste angelegt, in der zu jedem Objekt ein Markierungsfeld (schematisch in Fig. 4 dargestellt) mit zwei Datenfeldern vorgesehen sind, nämlich einem Datenfeld für ein valid-Flag und einem Datenfeld für ein scanned-Flag. Dann lautet der beispielhafte Such- und Markieralgorithmus:
markValidObjekts (int rootObjectlndex, int numberOfObjects)
{ // reset all flags (valid and scanned) (alle Flags zurücksetzen (valid und. scanned)) resetFlags(); // mark root object as valid (Root-Objekt als valid markieren) setValid(rootObjectIndex); .11 scan all objects (alle Objekte scannen(absuchen)) for (i=l; i <= numberOfObjects; i++) { // is valid flag of object i set and scanned flag not yet set?
// (ist valid-Flag des Objekts i gesetzt und das scanned Flag noch nicht?) if ( isValid(i) && üsScanned(i)) { // mark all referenced objects of i as valid // (alle durch Objekt i referenzierten Objekte als valid markieren) for (j=0; j < object ist[i].numberOfReferences(); j++) { // mark referenced object j as valid // (referenziertes Objekt j als valid markieren) setValid(objectList[i].getReferenceIndex(j)); } // all references of object i are scanned => set scanned flag of object i // (alle Referenzen von Objekt i sind angescannt worden => // => scanned Flag des Objekts i setzen) setScanned(i); }
(Algorithmus 1)
Bei dem vorangehenden Algorithmus 1 sind die beiden Datenfelder des Markierungsfeldes als ein valid-Flag und ein scanned-Flag ausgeführt (vgl. Fig. 4). Das valid-Flag eines Objekts ist jeweils dann gesetzt, wenn das Objekt referenziert ist. Das scanned-Flag ist jeweils dann gesetzt, wenn alle von diesem Objekt ausgehenden Referenzen abgesucht worden sind. Die Objekte sind von 1 bis n nummeriert, wobei das mit 1 nummerierte Objekt das Root-Objekt (Start-Objekt) ist. Nach Durchlauf des Algorithmus hat die Objektliste die in Fig. 4 dargestellte Gestalt und die Heap- Struktur die in Fig. 3 dargestellte Gestalt.
Das Root-Objekt (i=l) gilt in jedem Fall als referenziert und wird zu Begirm als valid gesetzt. Wahlweise kann es mehrere Root-Objekte geben. In diesem Fall werden zu Beginn alle Root-Objekte als valid gesetzt.
Anschließend werden, in der "-Schleife, alle durch das Root-Objekt referenzierten Objekte als valid gesetzt, um zu markieren, dass diese Objekte referenziert sind.
Nachfolgend wird das scanned-Flag des Root-Objekts (i=l) gesetzt, um zu markie- ren, dass alle vom Root-Objekt ausgehenden Referenzen abgescannt (abgesucht) worden sind.
Nun schreitet der Algorithmus 1 vom Root-Objekt (i=l, d.h. Objektindex 1) zum Objekt mit dem nächsthöheren Objektindex fort, d.h. zum Objekt Nr. 2, und prüft, ob das Objekt Nr. 2 valid ist, d.h. ob sein valid-Flag gesetzt ist. Falls das valid-Flag von Objekt Nr. 2 (abweichend von Fig. 3) nicht gesetzt ist, schreitet der Algorithmus 1 unmittelbar weiter zu Objekt 3. Falls hingegen (wie in Fig. 3) das valid-Flag von Objekt Nr. 2 gesetzt ist, werden alle vom Objekt 2 abgehenden Referenzen abgesucht und die von Objekt 2 aus referenzierten Objekte als valid markiert. Schließlich wird das scanned-Flag des Objekts 2 gesetzt, und anschließend schreitet der Algorithmus 1 weiter zu Objekt 3.
Das Verfahren wird bis zum letzten Objekt des Heaps (bei Fig. 3 Objekt 40) durchgeführt. Fig. 4 zeigt die Belegung der valid-Flags und scanned-Flags des Markie- rungsfeldes für die Heap-Struktur aus Fig. 3, nachdem Algorithmus 1 auf die Heap- Struktur angewandt worden ist.
Beim Beispiel aus Fig. 3 hat die Heap-Struktur Rückwärtsreferenzen, die bewirken, dass nicht alle Objekte der Heap-Struktur überprüft werden können, falls nur ein einziger Durchlauf des Durchsuchens des Heaps durchgeführt wird. Beispielsweise besteht zwischen dem Paar von Objekten (6, 9) eine Rückwärtsreferenz. Anders gesagt hat das Objekt 9 der zweiten Hierarchieebene einen höheren Objektindex als das Objekt 6 der dritten Hierarchieebene. Aus diesem Grund ist, wenn der Algorithmus 1 von Objekt 5, das bereits als valid und scanned markiert sein soll, zu Objekt 6 fort- schreitet, das Objekt 6 noch nicht als valid markiert. Folglich werden die von Objekt ausgehenden Referenzen nicht abgescannt, sondern der Algorithmus 1 schreitet di-
rekt fort zu Objekt 7, von dort zu Objekt 8 und weiter zu Objekt 9. Objekte 6, 7 und 8 bleiben unmarkiert, d.h. weder das valid-Flag noch das scanned-Flag wird gesetzt. Erst Objekt 9 ist wieder valid. Daher werden Objekte 6 und 8 als valid markiert und anschließend Objekt 9 als scanned markiert. Die scanned-Flags der Objekte 6 und 8 bleiben nicht gesetzt (vgl. auch Fig. 4). Bei Objekt 7 bleiben das valid-Flag und das scanned-Flag beide nicht gesetzt (s. Fig. 4).
Objekte in der Heap-Struktur, bei denen Rückwärtsreferenzen vorliegen, können dadurch überprüft werden, dass der Heap-Speicher so oft wie erforderlich abwech- selnd vorwärts (bei Fig. 3 von 1 bis 40) und rückwärts (bei Fig. 3 von 40 nach 1) durchsucht wird.
Während Fig. 3 die Heap-Struktur zeigt, nachdem der Heap-Speicher ein Mal vorwärts durchsucht worden ist, indem Algorithmus 1 darauf angewandet worden ist, zeigt Fig. 5 dieselbe Heap-Struktur, nachdem der Heap-Speicher zusätzlich ein Mal rückwärts durchsucht worden ist. Fig. 5 zeigt also die Heap-Struktur, nachdem ein Algorithmus 2 darauf angewendet worden ist, wie z.B. der folgende:
markValidObjekts (int rootObjectlndex, int numberOfObjects) { // reset all flags (valid and scanned) (alle Flags zurücksetzen) resetFlags();
// mark root object as valid (Root-Objekt als valid markieren) setValid(rootObjectIndex);
// scan all objects forward (alle Objekte vorwärts scannen) for (i=l; i <= numberOfObjects; i++) { // is valid flag of object i set and scanned flag not yet set? // (ist valid-Flag des Objekts i gesetzt und das scanned Flag noch nicht?) if ( isValid(i) && üsScanned(i) ) {
// mark all referenced objects of i as valid // (alle durch Objekt i referenzierten Objekte als valid markieren) for (j=0; j < objectList[i].numberOfReferences(); j++) { // mark referenced object j as valid // (referenziertes Objekt j als valid markieren) setValid(objectList[i].getReferenceIndex(j)); } // all references of object i are scanned => set scanned flag of object i // (alle Referenzen von Objekt i sind angescannt worden => // => scanned Flag des Objekts i setzen) setScanned(i);
}
// scan all objects backward (alle Objekte rückwärts scannen) for (i=numberOfObjects; i > 0; i~)
{ // is valid flag of object i set and scanned flag not yet set? // (ist valid-Flag des Objekts i gesetzt und das scanned Flag noch nicht?) if ( isValid(i) && üsScanned(i) ) { // mark all referenced objects of object i as valid // (alle durch Objekt i referenzierten Objekte als valid markieren) for (j=0; j < objectList[i].numberOfReferences(); j++) { // mark referenced object j as valid // (referenziertes Objekt j als valid markieren) setValid(objectList[i].getReferenceIndex(j)); } // all references of object i are scanned => set scanned flag of object i // (alle Referenzen von Objekt i sind angescannt worden // => scanned Flag des Objekts i setzen) setScanned(i); }
}
(Algorithmus 2)
Algorithmus 2 umfasst einen Vorwärts-Durchlauf (Vorwärts-Scan) durch den Heap- Speicher, der im Wesentlichen dem von Algorithmus 1 entspricht, und einen Rück- wärts-Durchlauf (Rückwärts-Scan). Beim Rückwärts-Durchlauf von Algorithmus 2 sind insbesondere für die Objekte 6 und 8 die scanned-Flags gesetzt worden, die nach dem Vorwärts-Durchlauf noch nicht gesetzt waren. Für Objekt 7 ist beim Rückwärts-Durchlauf das valid-Flag gesetzt worden. Die während des Rückwärts- Durchlaufs zusätzlich zum Vorwärts-Durchlauf gesetzten Flags sind auch aus Fig. 6 ersichtlich, in der das Markierungsfeld aus Fig. 4 gezeigt ist, nachdem zusätzlich ein Rückwärts-Durchlauf auf den Heap-Speicher angewendet worden ist.
Bei einem nachfolgend angegebenen bevorzugten Algorithmus 3 wird zusätzlich ein binäres drittes Datenfeld „End-Flag" verwendet, das geschaltet wird, sobald ein
Durchlauf des Durchsuchens des Heap-Speichers vorgenommen worden ist, bei dem keine Markierung vorgenommen worden ist. Nach Durchlauf des Algorithmus 3 hat die Objektliste die in Fig. 8 dargestellte Gestalt und die Heap-Struktur die in Fig. 7 dargestellte Gestalt.
markValidObjekts (int rootObjectlndex, int numberOfObjects)
{ // reset all flags (valid and scanned) (alle Flags zurücksetzen) resetFlags();
// define endFlag (Definiton des binären End-Flags) boolean endFlag;
// mark root object as valid (Root-Objekt als valid markieren) setValid(rootObjectΙndex); do {
// assume that this is the last scan (Annahme, dies sei der letzte Durchlauf) endFlag = true;
// scan all objects forward (alle Objekte vorwärts scannen ) for (i=l ; i <= numberOfObjects; i++)
{ // is valid flag of object i set and scanned flag not yet set? // (ist valid-Flag des Objekts i gesetzt und das scanned Flag noch nicht?) if ( isValid(i) && üsScanned(i) ) { // mark all referenced objects of i as valid // (alle durch Objekt i referenzierten Objekte als valid markieren) for (j=0; j < objectList[i].numberOfReferences(); j++) { // mark referenced object j as valid // (referenziertes Objekt j als valid markieren) setValid(objectList[i].getReferenceIndex(j));
// we have to do another run (scan) // (ein weiterer Durchlauf ist erforderlich, da eine // Markierung vorgenommen worden ist) endFlag = false; } // all references of object i are scanned // => set scanned flag of object i // (alle Referenzen von Objekt i sind abgescannt worden // => scanned Flag des Objekts i setzen) setScanned(i); } } if (endFlag = true)
{ // this was the last scan (das war der letzte Durchlauf) break; }
// assume that this is the last scan (Annahme, dies sei der letzte Durchlauf)
endFlag = true;
// scan all objects backward (alle Objekte rückwärts scannen) for (i=numberOfObjects; i > 0; i— ) { // is valid flag of object i set and scanned flag not yet set? // (ist valid-Flag des Objekts i gesetzt und das scanned Flag noch nicht?) if ( isValid(i) && ÜsScanned(i) ) { // mark all referenced objects of object i as valid // (alle durch Objekt i referenzierten Objekte als valid markieren) for (j=0; j < objectList[i].numberOfReferences(); j++) { // mark referenced object j as valid // (referenziertes Objekt j als valid markieren) setValid(objectList[i].getReferenceIndex(j));
// we have to do another run (scan) // (ein weiterer Durchlauf ist erforderlich, da eine // Markierung vorgenommen worden ist) endFlag = false; } // all references of object i are scanned // => set scanned flag of object i // (alle Referenzen von Objekt i sind abgescannt worden => // => scanned Flag des Objekts i setzen) setScanned(i); } } } while (endFlag = false)
(Algorithmus 3)
Der vorstehende Algorithmus 3 durchsucht den Heap-Speicher so oft abwechselnd vorwärts und rückwärts, bis ein Durchlauf (Scan) des Durchsuchens des Heap- Speichers vorgenommen worden ist, bei dem keine Markierung vorgenommen worden ist. Dies wird durch die (do, while)-Schleife verwirklicht, die als Bedingung für das Weitermachen hat, dass das End-Flag auf den booleschen Wert „false" geschaltet ist. Erst wenn in einem Durchlauf keine Markierung (valid-Flag) vorgenommen worden ist, bleibt das End-Flag auf den booleschen Wert „true" gesetzt und die (do, while)- Schleife und damit der Algorithmus wird beendet.
Fig. 7 zeigt die Heap-Struktur aus Fig. 1 und Fig. 3, nachdem Algorithmus 3 auf den zu Grunde liegenden Heap-Speicher angewendet worden ist, und Fig. 8 zeigt die zugehörige Objektliste. Alle Objekte der Heap-Struktur sind bereits als valid markiert. Der Zustand gemäß Fig. 7 und Fig. 8 ist auch der Zustand bei Beginn des letzten Durchlaufs der (do,while)-Schleife des Algorithmus 3. Da bei diesem Durchlauf keine Markierung an einem valid-Flag vorgenommen werden, bleibt das End-Flag auf „true" gesetzt, so dass die (do,while)-Schleife nach diesem letzten Durchlauf beendet wird.
Mit dem Verfahren, das durch den Algorithmus 3 implementiert ist, lassen sich somit für einen Heap-Speicher, dessen Heap-Struktur Rückwärtsreferenzen hat, sämtliche (noch) referenzierten Objekte finden. Gemäß Fig. 7 und 8 sind nur die nicht referenzierten Objekte 31, 32, 33, 29, 30, 23, 24 mit nicht markiertem valid-Flag übrig geblieben. Alle referenzierten Objekte sind markiert.