Visualisierung von Java-Programmen! 27.08.2005 Seminar “Softwarevisualisierung” Technische Universität Kaiserslautern Betreuer: Dr. Andreas Kerren Visualisierung von Java-Programmen von Samuel Strupp Samuel Strupp (349495)! 1 Visualisierung von Java-Programmen! 27.08.2005 Inhaltsangabe 1. Einführung! ! ! ! ! ! ! ! 2. Programmvisualisierung - eine Definition! ! 2.1 Visualisierung! ! ! ! ! ! ! ! ! ! ! Seite 3 ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! Seite 3 Seite 3 Seite 3 Seite 4 Seite 5 2.2 Softwarevisualisierung!! ! ! ! 2.2.1 Algorithmusvisualisierung!! ! 2.2.2 Programmvisualisierung! ! ! ! ! ! ! ! ! ! ! ! 3. Was kann man visualisieren?! ! ! 3.1 Visualisierungsideen! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! Seite 6 Seite 6 Seite 8 3.2 Probleme! ! ! ! ! ! ! ! 4. JIVE! ! ! ! ! 4.1 Über JIVE! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! 4.2 Anforderungen an JIVE! ! ! 4.3 Lösungen in JIVE! ! ! ! ! 4.3.1 Verschiedene Ansichten! ! ! 4.3.2 Kontursemantiken für Java! 4.4 Die Architektur von JIVE! ! ! 4.5 Kurzer Ausblick in die Zukunft! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! Seite 18 Seite 18 Seite 18 Seite 18 Seite 20 Seite 24 ! ! ! ! ! ! ! 5. JOVE!! ! ! ! 5.1 Über JOVE!! ! ! ! ! ! ! ! ! ! ! 5.2 Anforderungen an JOVE! ! 5.3 Lösungen in JOVE! ! ! 5.4 Die Architektur von JOVE! ! 5.5 Kurzer Ausblick in die Zukunft! Seite 10 Seite 10 Seite 10 Seite 11 Seite 11 Seite 13 Seite 15 Seite 17 6. Zusammenfassung und eigene Meinung!! ! ! ! ! Seite 24 7. Literaturangaben! ! ! ! ! ! Seite 25 Samuel Strupp (349495)! ! ! ! ! ! 2 Visualisierung von Java-Programmen! 27.08.2005 1. Einführung Dieses Dokument beschäftigt sich mit der Programmvisualisierung, also der grafischen Darstellung von Programmzuständen und Programmabläufen. Zunächst wird der Begriff der “Programmvisualisierung” erklärt, was darunter genau zu verstehen ist und wo der Unterschied zu anderen Visualisierungen besteht. Im darauf folgenden Abschnitt werden verschiedene Ideen der Programmvisualisierung besprochen und welche Probleme bei der Visualisierung häufig auftreten. Danach werden die zwei Visualisierungswerkzeuge JIVE und JOVE für Java-Programme vorgestellt. Das Interesse liegt dabei auf deren Fähigkeiten die Programmabläufe und Programmstrukturen aufzunehmen und über geeignete Grafiken sinnvoll darzustellen. Es wird also speziell die Visualisierung von objekt-orientierter Software betrachtet, welche in der Sprache Java geschrieben wurde! 2. Programmvisualisierung - eine Definition Zunächst wird der Begriff der „Visualisierung” erläutert, um danach über die Softwarevisualisierung auf die „Programmvisualisierung” und den Unterschied zur „Algorithmusvisualisierung” zu kommen. 2.1 Visualisierung Eine Gemeinsamkeit aller Visualisierungen ist, Informationen unter einem bestimmten Gesichtspunkt zu transformieren, um sie in einer geeigneten Sicht dem Anwender in einer möglichst nützlichen Form zu präsentieren. Die Transformation beinhaltet hierbei oft das geschickte Sichtbarmachen von nicht sichtbaren Vorgängen, Ereignissen oder Zuständen. Im wesentlichen gibt es drei Arten von Visualisierungen: die Visualisierung als Verschriftlichung, die Visualisierung als Übersetzung ins Nonverbal-Optische und schließlich die Visualisierung als Illustration. Das Ziel jeder Visualisierung ist die Bildung eines mentalen Modells. 2.2 Software-Visualisierung Die Softwarevisualisierung hat das Ziel, die Funktionsweise einer Software, also deren Arbeitsweise und Struktur, verständlich zu machen. Dies wird hauptsächlich über Techniken der Typographie oder der Kinematographie zu erreichen versucht. Dabei beschreibt die Typographie das Darstellen von Text. Die Kinematographie bezeichnet die Verfahren zur Aufnahme und Wiedergabe von bewegten Bildern, bei denen man in rascher Folge Bildreihen erzeugt und dann zur Betrachtung in analoger Weise wiedergibt. Es werden also hauptsächlich Text und animierte Grafiken für die Visualisierungen von Software benutzt. Im wesentlichen gibt es zwei Arten von Softwarevisualisierung: die Algorithmenvisualisierung und die Programmvisualisierung [8] (siehe Abbildung 2.1). Samuel Strupp (349495)! 3 Visualisierung von Java-Programmen! 27.08.2005 Software-Visualisierung Algorithmen-Visualisierung statische AlgorithmenVisualisierung dynamische AlgorithmenVisualisierung Programm-Visualisierung statische CodeVisualisierung dynamische CodeVisualisierung statische DatenVisualisierung dynamische DatenVisualisierung Abbildung 2.1: Aufteilung der einzelnen Software-Visualisierungs-Gebiete 2.2.1 Algorithmusvisualisierung Im wesentlichen findet man bei der Visualisierung von Algorithmen nur strukturelle Ähnlichkeiten zwischen den Darstellungselementen und dem tatsächlich implementierten Verfahren. Die Algorithmusvisualisierung ist also die Darstellung von Software auf einem hohen Abstraktionsgrad. Das Teilgebiet lässt sich weiter in statische- und dynamische Algorithmenvisualisierung untergliedern, wobei letzterer Visualisierungstyp auch als Algorithmenanimation bezeichnet wird. Es gibt zum Beispiel eine Menge AlgorithmenAnimationen, welche die Arbeitsweise verschiedener Sortierverfahren mit Hilfe einfacher animierter Darstellungen erläutern. Ein einfaches Beispiel für eine statische Algorithmenvisualisierung ist ein Programmablaufplan (Flussdiagramm), welcher in groben Zügen die Funktionsweise des Programms bzw. des Algorithmus beschreibt. In der Regel werden nur wesentliche Verfahrensschritte in statischer Form unter Verwendung einer grafischen Symbolik dargestellt. Jeder Bezug auf eine konkrete Programmiersprache oder Implementierungsvariante wird dabei vermieden. In Lehrbüchern findet dieser Visualisierungstyp oft Anwendung in statischer Form. Werkzeuge, welche Algorithmusvisualisierungen dynamisch darstellen, sind meist unflexibel im Bezug auf Verbesserungen oder Änderungen des Algorithmus, da dieser meist fest im Werkzeug selber implementiert ist und nur selten über eine Beschreibungssprache interpretiert wird. Die meisten Algorithmusvisualisierungen können daher auch nur mit unterschiedlichen Daten arbeiten die normalerweise auch in einer be-stimmten Struktur vorliegen müssen. Das Anwendungsgebiet dieses Visualisierungstyps dient deswegen auch hauptsächlich zum Verstehen des visualisierten Algorithmus und wird somit hauptsächlich in der Lehre genutzt. Abbildung 2.3: Beispiel einer Code-Visualisierung Samuel Strupp (349495)! 4 Visualisierung von Java-Programmen! 27.08.2005 2.2.2 Programmvisualisierung Der Unterschied zwischen der Programmvisualisierung und der Algorithmusvisualisierung soll durch eine Definition des Begriffes Algorithmus verdeutlicht werden: Algorithmen sind exakt formulierte Rechenvorschriften bzw. Vorgehensweisen, die auf Computern als ausführbare Programme implementiert und auf diese Art für die Lösung von Aufgaben angewandt werden können. Ein Algorithmus besteht somit aus einer wohldefinierten endlichen Folge von elementaren Rechenoperationen und Entscheidungen, um aus einer bestimmten Menge von Eingabegrößen das gewünschte Resultat zu erzeugen. Programme sind also nichts anderes als Implementierungsvarianten von bestimmten Algorithmen. Bei der Programmvisualisierung geht es also um die Darstellung einer konkreten Implementierung eines oder mehreren Algorithmen. Der wesentliche Unterschied der beiden Visualisierungstypen liegt also im Abstraktionsgrad. Die Programmvisualisierung hat in den meisten Fällen eine geringere Abstraktion, da sie ja die konkrete Implementierung und nicht die Idee eines Algorithmus darstellen muss. Die Programmvisualisierung kann man in zwei weitere Gruppen aufteilen, nämlich die Codevisualisierung und die Datenvisualisierung. Diese Unterscheidung macht Sinn, da zur Berechnung von Daten oft charakteristische Datenstrukturen im Programm verwendet werden. Diese Strukturen kann man wiederum auf spezielle Weisen darstellen, um die Funktionsweise einer solchen Berechnung bei der konkreten Implementierung noch besser vermitteln zu können. Es gibt viele Möglichkeiten, Daten intuitiver darzustellen. Zum Beispiel kann man ListenEinträge durch Rechtecke, deren Inhalt durch die entsprechenden Inhalte gefüllt sind, darstellen. Die Verkettung der einzelnen Elemente wird dabei einfach durch Pfeile gekennzeichnet (siehe Abbildung 2.2). Wird jetzt an der Liste etwas hinzugefügt, gelöscht oder vertauscht, kann dieser Vorgang bei der dynamischen Datenvisualisierung durch entsprechende Animationen, farbige Kennzeichnungen oder Symbole leicht verständlich dargestellt werden. 3 6 8 2 12 5 Abbildung 2.2: Beispiel einer Daten-Visualisierung Die Codevisualisierung kann zum Beispiel das Darstellen des entsprechenden Programmcodes sein (siehe Abbildung 2.3). Das farbige Hervorheben des gerade aktiven Programmteils kann als Code-Animation interpretiert werden. Aber auch die Darstellung von Rekursionsbäumen bei rekursiven Verfahren oder die Darstellung eines SequenzDiagramms kann zu diesem Visualisierungstyp gehören. Oft ist es aber auch nicht eindeutig, zu welchem Teilgebiet eine Visualisierung gehört, da sich die beiden Gebiete bei solchen Darstellungen überschneiden. Zum Beispiel das Darstellen von Klassenbeziehungen mit konkreten Instanzen und Werten kann sowohl der Code- als auch der Datenvisualisierung zugeordnet werden, denn zum einen wird die Struktur des Programms, also dessen Objekte und ihre Beziehungen, dargestellt und zum anderen werden auch die einzelnen instanziierten Objekte mit deren Daten angezeigt. Ob die Objekte jetzt spezielle Datenstrukturen repräsentieren oder Funktionsteile des Programms sind, wird in solch einem Fall nicht unterschieden. Samuel Strupp (349495)! 5 Visualisierung von Java-Programmen! 27.08.2005 In den folgenden Kapiteln werden hauptsächlich nur noch Programmvisualisierung von Java-Programmen betrachtet und dabei anhand zweier Beispiele (Kapitel 4 und 5) vor allem auf Eigenschaften wie Objekt-Orientierung und Threads eingegangen. 3. Was kann man visualisieren? In diesem Kapitel werden verschiedene Ideen vorgestellt, welche Teile eines Programms visuell repräsentiert werden können, was man dabei beachten sollte und was es für Probleme bei der Visualisierung auftreten können. 3.1 Visualisierungsideen Wie im vorhergehenden Kapitel beschrieben, gibt es bei der Programmvisualisierung zwei unterschiedliche Arten von Visualisierungen, die Datenvisualisierung und die Codevisualisierung. Die Idee bei der Visualisierung ist es, die Struktur und Art des Programms auf sinnvolle Weise grafisch darzustellen. Es ist z.B. möglich, jede Instanz eines Objektes in Java einzig und allein durch das Füllen eines Kästchen in einem Gitterfeld zu repräsentieren. Allerdings ist dies wohl eher wenig aussagekräftig, da man zwar eine grafische Darstellung der instanziierten Objekte hat, aber aus dieser Darstellung nicht viel Informationen entnehmen kann. Der Informationsgehalt einer Darstellung ist mit ausschlaggebend für die Qualität einer Visualisierung. Der zweite wichtige Punkt ist die Interpretierbarkeit der Visualisierung. Eine Visualisierung, die zwar viele wertvolle Informationen beinhaltet, dies aber auf eine Weise darstellt, die vom Menschen gar nicht oder nur sehr mühsam interpretiert werden kann, ist wertlos. Es ist also wichtig, sich Gedanken darüber zu machen, was man darstellen kann, was sinnvoll ist, dargestellt zu werden und vor allem, wie man es dann darstellt. Es gibt viele Ideen für die Visualisierung von Programmen. Zunächst werden einige Ideen betrachtet, welche eher der Codevisualisierung zuzuordnen sind: Die Darstellung des Quellcodes ist sicher eine der einfachsten und geläufigsten Programmvisualisierungen. Viele statische Programmvisualisierungen bauen ihre Darstellungen des Programms anhand des Quellcodes auf (siehe Abbildung 2.3). Die Darstellung von Objektstrukturen wird auch häufig eingesetzt, um zum Beispiel die Objektstruktur eines Programms in UML wiederzugeben. Solche Diagramme lassen sich relativ einfach anhand des Quellcodes erzeugen. Eine gut strukturierte und somit lesbare Darstellung dieser Diagramme wird allerdings um so Abbildung 3.1: Mögliche Visualisierung einer Objektstruktur als UML-Diagramm komplizierter, je komplexer die Objektstruktur wird (Beispiel siehe Abbildung 3.1). Instanzen von Objekten können auch visualisiert werden. Dies ist bei der dynamischen Codevisualisierung sehr sinnvoll, um das Verständnis für den Ablauf des aktuellen Samuel Strupp (349495)! 6 Visualisierung von Java-Programmen! 27.08.2005 Programms zu erhöhen. Auch für das Debuggen von Programmen kann eine solche Visualisierung gut genutzt werden. Instanzen können auf verschiedene Weisen dargestellt werden, zum Beispiel durch einen einfachen Zähler der Instanzen eines bestimmten Objekts oder wieder durch ein Diagramm, in dem durch Pfeile auch aktuelle Objektverknüpfungen dargestellt werden können (siehe beispielsweise Abbildung 4.3 in Kapitel 4). Die bildliche Repräsentation von Methodenaufrufen sind oft hilfreich, um die Kommunikation zwischen Objekten nachvollziehen zu können. Eine mögliche Visualisierung von Methodenaufrufen zwischen verschiedenen Klassen und deren Instanzen ist in der Abbildung 3.2 dargestellt. Sie stammt aus dem Visualisierungswerkzeug „Jinsight“ [4] von Wim de Pauw und weiteren. Ob dies jetzt eine sinnvolle Visualisierung ist oder nicht, hängt immer vom Kontext sowie dem Betrachter ab. Den Ablauf eines Programms festzuhalten und gleichzeitig visuell darzustellen, erleichtert es enorm, die Entstehung von Fehlern nachvollziehen zu können oder den Ablauf im allgemeinen zu verstehen (Beispiel in Abbildung 4.1). Oft werden hierfür Sequenzdiagramme gezeichnet. Nicht nur der Ablauf des Programms selbst kann interessant sein, sondern auch, was daraus abgelesen werden kann. So kann das Visualisierungswerkzeug „JaVis” [3], das von Katharina Mehner an der Universität von Paderborn entwickelt wurde, Deadlocks im Programm erkennen und diese selbstverständlich auch visualisieren (Abbildung 3.3). Abbildung 3.2: Jinsight Visualisieung von Methodenaufrufen (Linien) Abbildung 3.3: Deadlock in JaVis Eine recht interessante Sache ist die Darstellung von Threads. (Beispiel in Abbildung 4.1, 4.5 oder 5.1) Die Darstellung von Threads in einer Visualisierung ist sehr wichtig, da ohne diese Komponente wichtige Teile, die zum Verständnis der Entstehung der Visualisierung beitragen, fehlen. Man kann nicht nur Threads als Instanzen darstellen, sondern zum Beispiel auch zu jedem Thread seine Ablaufgeschichte im Programm. Eine weitere Möglichkeit, um Informationen über ein Programm zu bekommen, ist das Visualisieren von Kommunikationen zwischen Betriebssystem und dem Programm. Dies kann die Inanspruchnahme fremder Ressourcen durch das Programm verdeutlichen und zudem den Grad der Abhängigkeit zur Laufzeit. Ideen von Datenvisualisierungen: Die Visualisierung von Daten, die in einem Programm genutzt werden bzw. vorhanden sind, kann auch sehr nützlich sein. Man kann sich zum Beispiel vorstellen, dass man Variablenwerte wie numerische Werte sowie Zeichen und Texte aber auch komplexe Datenstrukturen wie Bilder, Videos und sogar Töne visualisiert, um den Ablauf eines Programms, das mit diesen Daten arbeitet, besser verstehen zu können. Allerdings ist hier die Frage, ob dies wirklich noch zur Programmvisualisierung gehört. Bei der Darstellung von Variablen-Werten ist das weniger die Frage, aber bei der Darstellung von Bildern, Samuel Strupp (349495)! 7 Visualisierung von Java-Programmen! 27.08.2005 Tönen und Videos wohl schon, da dies sehr spezielle Anforderungen sind und vielleicht eher in den Bereich der Algorithmusvisualisierung fallen. Klar abgrenzen kann man dies allerdings nicht, da auch spezialisierte Visualisierungen ihren Platz in allgemeineren Visualisierungs-Umgebungen haben sollten. Datenstrukturen können auch anhand des Quellcodes erkannt werden und somit spezielle Darstellungsformen gewählt werden. Einfache Beispiele hierfür sind zum Beispiel einfach und doppelt verkettete Listen (Abbildung 2.2), Bäume (Abbildung 4.3) oder auch der Aufbau des Stacks. Die exakte Darstellung des vom Programm zugeteilten Speichers und dessen Belegung kann auch sehr Informativ sein, wenn Interesse an der Low-Level-Ebene des Programms besteht. Allerdings wird diese Verteilung der Daten und die Speicherzuweisung zum Großteil vom Betriebssystem erledigt und ist zudem bei heutigen Systemen sehr dynamisch. Das hat zur Folge, dass die Informationen darüber erstens nur schwer zugänglich sind und zweitens sich die Struktur nur schwer exakt abbilden lässt. Eine Darstellung von Pufferzuständen oder Streamdaten kann, wenn es in geeigneter Weise erfolgt, auch sehr zu dem Verständnis eines Programmablaufs dienen. Allerdings sind auch hier wieder sehr spezielle Anforderungen an das Visualisierungs-Werkzeug zu stellen, und die Menge an Daten eines Streams ist auch sehr schwer auf geeignete Weise zu visualisieren. Eine weitere Möglichkeit der Visualisierung liegt in der Möglichkeit, externe Kommunikation, die über das Betriebssystem hinaus geht, darzustellen. Zum Beispiel könnte man Netzwerkverbindungen und die damit verbundenen Datenflüsse visualisieren. Das Programm Azureus [9] beinhaltet zum Beispiel solch eine Visualisierung. Dabei handelt es sich um ein File-Sharing-Programm auf Java-Basis, welches es erlaubt, gleichzeitig von mehreren Quellen eine Datei herunter zu laden. Die Verbindungsstruktur ist in der Abbildung 3.4 repräsentiert. Abbildung 3.4: Visualisierung von Netzwerkverbindungen + Datenübertragungen in Azureus Dies ist natürlich nur ein kleiner Ausschnitt von Ideen, welche Teile eines Programms visualisiert werden können. Zudem ist die Vielfalt der Darstellungsmöglichkeiten enorm. 3.2 Probleme Es gibt natürlich auch Probleme bei der Programmvisualisierung, welche nicht so einfach zu lösen sind. In diesem Kapitel werden ein paar typische Probleme beschrieben. 1. Skalierbarkeit der Darstellungen Viele Ansichten von Visualisierungen sind sehr detailliert und umfangreich. Das hat zur Folge, dass bei der Visualisierung von sehr großen Systemen die Darstellungen sehr komplex, umfangreich und für den Betrachter oft unüberschaubar werden. Es ist beispielsweise wenig sinnvoll, einen Benutzer in einem Objektdiagramm, welches 20 Bildschirme füllen könnte, nach einem bestimmten Objekt suchen zu lassen. Dieser würde eine solche Darstellung zudem sehr schnell als unbrauchbar deklarieren, wenn er die für ihn interessanten Informationen nicht finden kann. Eine Lösung für dieses Problem könnte Samuel Strupp (349495)! 8 Visualisierung von Java-Programmen! 27.08.2005 es sein, interessante Informationen zu filtern und nur diese darzustellen. Allerdings ist dies nicht unbedingt immer realisierbar. 2. Geschwindigkeit Visualisierungen benötigen oft viel Rechenleistung, um die Grafiken zu erzeugen. Aber auch das Sammeln von Daten, welche für die Visualisierung benötigt werden, kann sehr aufwändig sein. Externe Visualisierungs-Werkzeuge verlangsamen so den Ablauf eines Programms oft erheblich. Das kann zur Folge haben, dass manche Programme nicht korrekt funktionieren können, da sie eine gewisse Grundperformance benötigen, um ihre Aufgaben korrekt durchführen zu können, z.B. Multimediaprogramme, die Ton- oder Videodaten in Echtzeit verarbeiten müssen. Desweiteren können Programme, die stark von Benutzereingaben abhängen, so extrem verlangsamt werden, dass eine sinnvolle Nutzung nicht mehr gegeben ist. Bei der Visualisierung ist also immer darauf zu achten, dass die benötigte Performance des zu visualisierenden Programms nicht zu sehr eingeschränkt wird. 3. Spezialisierung Die Spezialisierung von Programmvisualisierungssystemen auf bestimmte Plattformen oder Programmiersprachen ist kein eigentliches Problem. Allerdings macht es deutlich, dass es nicht einfach, wahrscheinlich sogar unmöglich ist, generelle VisualisierungsWerkzeuge für alle Programme zu erstellen, welche gleichzeitig sinnvolle Abstraktionen des Programms darstellen. So kann ein Visualisierungs-Werkzeug, das auf das Darstellen von objektorientierten Programmen spezialisiert ist, natürlich nicht viel mit einem rein prozeduralem Programm anfangen. 4. Speicherplatzbedarf Viele Visualisierungen basieren auf einer enormen Datenmenge, welche zum einen im Speicher gehalten werden muss und auf die zum anderen möglichst schneller Zugriff möglich sein muss. So kann z.B. bei einer Visualisierung, welche die komplette Ablaufgeschichte eines Programms speichern soll, der Speicherbedarf riesig sein. Deshalb sollte man immer darauf achten, dass nur benötigte Daten gespeichert werden und dass eventuell redundante Daten optimiert gespeichert werden. 5. Implementierungsart Es gibt natürlich zwei verschiedene Wege, um die Abläufe innerhalb eines Programms darzustellen. Entweder das Programm liefert selbst Visualisierungen zu seinem Ablauf oder die Daten werden von einem externen Programm bezogen und dann visualisiert. Die erste Variante ist oft recht aufwändig, da sie beinhaltet, dass evtl. der komplette Quellcode verändert werden muss, um Programmvisualisierungen zu implementieren. Das externe Programm hingegen muss auf irgendeinem Wege an Daten über das Programm heran kommen. Dies kann bei Java zum Beispiel über die JPDA (Java Plattform Debugger Architektur) erfolgen. 6. Vergänglichkeit von Eingaben Nicht alle Programme eigenen sich zur Visualisierung. Wie schon erwähnt, verlangsamen Visualisierungen ein Programm manchmal sehr stark. Wenn dieses Programm, dann von vergänglichen Daten, wie zum Beispiel einem Live-Video-Stream abhängt, diese Daten aber nicht schnell genug verarbeiten kann, dann liefert die Visualisierung natürlich auch keine Darstellung eines korrekten Ablauf des Programms. Zudem können viele Situationen, die durch eine bestimmte Datenkonstellation erzeugt werden, nicht wieder hergestellt werden, da genau diese Daten so nicht mehr zur Verfügung stehen. Dies macht das Analysieren oder Vergleichen von Programmzuständen sehr schwierig bis unmöglich, da die entsprechenden Situationen nicht reproduziert werden können. Samuel Strupp (349495)! 9 Visualisierung von Java-Programmen! 27.08.2005 4. JIVE 4.1 Über JIVE Der Name JIVE bedeutet „Java Interaktive Visualization Environment” also in Deutsch etwa „interaktive Java Visualisierungs-Umgebung” [1]. Es gibt leider sehr viele Programme, die den Namen „JIVE” tragen. Das JIVE, über welches hier gesprochen wird, wurde von Paul Gestwicki und Bharat Jayaraman an der Universität von Buffalo entwickelt. Es handelt sich dabei um ein Programmvisualisierungswerkzeug, welches objektorientierte Java-Programme visualisiert. Objektorientierte Programme unterscheiden sich von prozeduralen Programmen in zwei wesentlichen Punkten: (1) Objekte sind nicht nur Datenstrukturen, sondern dienen auch als Umgebungen, in denen Prozeduraufrufe stattfinden. (2) Objektorientierte Programme verlangen die Nutzung von mehreren kleineren Methoden. Daraus resultieren dann kompliziertere Wechselbeziehungen zwischen den Objekten. Das Ablaufverständnis eines objektorientierten Java-Programms wird durch das zur Verfügung stellen von Darstellungen der Objektstruktur sowie der Geschichte der Objektinteraktionen verbessert. Obwohl diese Ansichten grundlegend für das Verständnis sind, gibt es laut Gestwicki und Jayaraman kein Visualisierungssystem für objektorientierte Programme, welches diese grundlegenden Kriterien realisiert. Die Visualisierungen aus JIVE sind generell anwendbar auf alle objektorientierten Sprachen nicht nur auf Java im speziellen. Allerdings gibt es bei jeder Sprache Komplikationen und kleine Nuancen, welche bewältigt werden müssen, so auch bei Java. 4.2 Anforderungen an JIVE Gestwicki und Jayaraman haben sieben Hauptanforderungen für ein System, das Laufzeitzustände von Java visualisieren soll, aufgestellt: 1. Bildliche Darstellung von Objekten als Umgebungen. Es gibt bereits mehrere Werkzeuge, welche die Methodenaufrufsequenz bildlich darstellen können, was wiederum die Inspektion von objektinternen Details unterstützt. Allerdings stellen diese Werkzeuge weder die gesamte Struktur des Objektes, noch die Methodenaufrufe in diesen Objekten dar, und folglich fehlen wichtige Beziehungen in der Visualisierung. 2. Das zu Verfügung stellen von mehreren Ansichten der Laufzeitzustände. Der momentane Ablaufzustand des Programms sollte auf verschiedenen Detailstufen wahrnehmbar sein, um besser Vergleich durchführen zu können. Die Ansichten sollten sowohl dem Anfänger zum Verstehen und Lernen dienen, wie auch einem Profi, der die Visualisierungen zum Debuggen benutzt. 3. Festhalten der Ablaufgeschichte und Methodeninteraktion. Die Geschichte des Programmablaufs sollte wahrnehmbar sein, indem Notationen wie Zeitsequenzdiagramme oder Interaktionsdiagramme benutzt werden. Die Visualisierung der Programm-Geschichte sollte interaktiv sein, sodass der Benutzer einen Punkt der Programmgeschichte auswählen kann, den er betrachten will. Zum Beispiel sollte das Auswählen eines Methodenaufrufes in einem Sequenzdiagramm das Visualisierungs-Werkzeug veranlassen, den Laufzeitzustand, an dem die Methode aufgerufen wurde, darzustellen. 4. Unterstützen von Vorwärts- und Rückwärtsausführungen. Es sollte möglich sein, interaktiv vorwärts und rückwärts durch den Programmablauf zu schreiten. Diese Fähigkeit Samuel Strupp (349495)! 10 Visualisierung von Java-Programmen! 27.08.2005 ist besonders für das Debuggen wichtig, weil das Auftreten eines Fehlers normalerweise erst nach einem gewissen Punkt erkannt wird. Auch sollte die Schrittgröße variabel sein, (Statement-Ebene, Methoden-Ebene oder Breakpoints). Desweiteren sollten diese Möglichkeiten auch bei Multithread-Programmen unterstützt werden. 5. Unterstützen von Abfragen des Laufzeitzustandes. Eine der wichtigsten Anforderungen für das Programmdebuggen ist das Verständnis, wie sich die Variablenwerte ändern. Es sollte möglich sein, den Laufzeitzustand nach Eigenschaften von Variablen abzufragen (wurde Variable geändert oder hat die Variable einen bestimmten Wert angenommen?). Aus dieser Anforderung resultiert eine abfragbare Datenbank von Laufzeitzuständen. 6. Erzeugen von klaren und lesbaren Zeichnungen. Die Visualisierungsumgebung sollte automatisch Diagrammkomponenten anordnen, damit Objektstrukturen und Methodenaufrufsequenzen dargestellt werden können. Individuelle Visualisierungen von oft benutzten Typen, wie z.B. Arrays, Listen und Tabellen, sollten zur Verfügung stehen. 7. Die existierende Java Virtual Machine benutzen. Es ist wichtig, dass das Visualisierungsystem auf der existierenden Java Virtual Machine (JVM) läuft und dass keine extra Implementierung eines Java Interpreters benötigt wird. Eine angepasste JVM-Implementierung wäre sehr schwer auf dem aktuellen Stand zu halten. Zusätzlich sollte es möglich sein, Programme mit einer grafischen Benutzeroberfläche zu visualisieren, welche mit Bibliotheken wie Swing oder AWT erstellt wurden. 4.3 Lösungen in JIVE Ein grundlegendes Problem bei Visualisierungswerkzeugen ist die Frage, was eigentlich genau visualisiert werden soll, und vor allem, wie es einfach und verständlich dargestellt werden kann. JIVE bietet dazu verschiedene Visualisierungen, welche die verschiedenen Aspekte eines Programms darstellen sollen. Diese verschiedenen Ansichten werden in 4.3.1 besprochen. Im Punkt 4.3.2 werden die Besonderheiten von Java in Augenschein genommen, um die Kontursemantiken dieser objektorientierten Sprache visualisieren zu können. 4.3.1 Verschiedene Ansichten Kompakte Ansichten (Compact Views) Die kompakte Ansicht wird anhand eines gegebenen rekursiv strukturierten binären Suchbaumes bildhaft erläutert. Jeder Zweig des Baumes sei selbst wieder ein Unterbaum. Abbildung 4.1 gibt eine mögliche Visualisierung eines Zustandes der Programmausführung. Der obere Teil des Bildschirmfotos zeigt eine Konturdiagrammvisualisierung der Objektstruktur. Dies wird „kompakte Ansicht” genannt, da nicht alle Details von jeder Kontur angezeigt werden. Die Abbildung 4.1: Kompakte Ansicht und Sequenzdiagramm Knotenpunkte des Baumes werden als dunkel umSamuel Strupp (349495)! 11 Visualisierung von Java-Programmen! 27.08.2005 randete Instanz-Konturen angezeigt. Jeder ist nach der Klasse benannt, in der er instanziiert wurde (in diesem Fall DupTree) und ein Instanzenzähler wurde hinzugefügt, um individuelle Objekte unterscheiden zu können. Es gibt eine statische Kontur für jede Klasse, die von dem Java-System geladen wurde. Eine statische Kontur ist eingebettet in die statische Kontur ihrer Superklasse, und da alle Klassen in Java Unterklassen von java.lang.object sind, sind alle statischen Konturen eingebettet in die statische Kontur von java.lang.object. Die Pfeile im Diagramm sind strukturelle Verknüpfungen, welche anzeigen sollen, dass eine Referenz von einem Objekt zum anderen besteht. Diese Verknüpfungen stellen die Verbindung zwischen den Knotenpunkten der binären Baumstruktur dar und entsprechen genau den linken und rechten Referenzvariablen des binären Suchbaumes. Sequenzdiagramme Der untere Teil von Abbildung 4.1 zeigt das Sequenzdiagramm des visualisierten Programms. Sowohl die statischen Konturen als auch die Objekt-Konturen werden als Kontext am oberen Ende des Sequenzdiagramms angezeigt. Die Methodenaktivierungen werden als Rechtecke auf den vertikalen Lebenslinien ihres entsprechenden Kontextes angezeigt. Die main-Methode ist die ganz links dargestellte Methodenaktivierung. Die Methoden welche mit „<init>” benannt sind, kennzeichnen die Erzeugung eines neuen Kontextes durch den Aufruf des jeweiligen Konstruktors. Java benutzt dieses Symbol intern, um auf den Konstruktor zu verweisen und den Initializeraufruf zu instanziieren, deshalb wird diese Notation auch hier benutzt. Der Benutzer kann durch das Sequenzdiagramm scrollen und Kontexte oder Methodenaktivierungen auszuwählen. JIVE wird daraufhin zu dem entsprechenden Konturendiagramm springen, welches diesen Zustand repräsentiert. Detaillierte Ansichten Eine detailierte Ansicht zeigt den kompletten Zustand eines Objekts, und beinhaltet alle Elemente und Methoden, von denen geerbt wurde. Die Element-Tabellen zeigen die definierten Variablen innerhalb des Konturenkontextes. Die Tabellen können so konfiguriert werden, dass sie auch Methodendefinitionen anzeigen. Es ist nicht generell nötig, die Element-Tabellen für jede Kontur Abbildung 4.2: Detaillierte Ansicht aus einer anzuzeigen. Das Bildschirmfoto in älteren Version von JIVE (0.21) Abbildung 4.2 zeigt, wie strukturelle Verknüpfungen zwischen Konturen dargestellt werden. Die Verknüpfung startet bei der Zelle der entsprechenden Variable und wird hin zu der referenzierten Kontur gezeichnet. Dies erlaubt strukturelle Verknüpfungen zu dem richtigen implementierten statischen Variablenbereich bei Vererbung. MethodenKonturen repräsentieren Methoden-Aufrufe. Methoden-Konturen haben Element-Tabellen, welche die lokalen Variablen einschliesslich der Parameter der Methode beinhalten. Die Element-Tabellen von Methoden-Konturen oder Instanz- und statischen Konturen können auf Wunsch versteckt oder angezeigt werden. Methoden-Konturen sind in ihrem statischen Kontext eingebettet. JIVE zeigt den Quellcode für die aktuelle Methode an, und die Samuel Strupp (349495)! 12 Visualisierung von Java-Programmen! 27.08.2005 momentan aktive Quellcodezeile wird sichtbar markiert. Es werden Markierungsfarben für jeden Thread benutzt, um den momentanen Ausführungszustand klar ablesen zu können. Aufruf-Pfad-Ansichten und minimierte Ansichten Wenn Konturen minimiert sind, dann werden sie als einfache Punkte dargestellt, wie in Abblidung 4.3 gezeigt. In einer Aufruf-Pfad-Ansicht werden Konturen mit Methodenaufrufen in der Kompakt-Ansicht, und die ohne Methodenaufrufe in der minimierten Ansicht angezeigt. Eine Aufruf-Pfad-Ansicht ist geeignet, um sich auf einen bestimmten Methodenaufruf oder eine ganze Reihe von Methodenaufrufen konzentrieren zu können. Die allumfassende Struktur ist immer sichtbar, aber visuell komplexe Details werden ausgeblendet. Wenn manche Details uninteressant sind für den Benutzer, dann kann er oder sie die komplette Kontur aus der Ansicht ausblenden. Abbildung 4.3: Es gibt Funktionen, welche alle Konturen expandieren, Minimierte Ansicht alle Konturen minimieren oder die Aufruf-Pfad-Ansicht anzeigen. Zusätzlich kann der Benutzer die einzelnen Konturen auswählen und wählen, ob er sie expandieren, minimieren oder die ElementTabelle ein- oder ausblenden möchte. Abbildung 4.3 zeigt ein Kontur-Diagramm, das komplett minimiert wurde. Die komplett minimierte Ansicht ist sinnvoll, wenn man nur die Gesamtstruktur betrachten will, ohne irgendwelche internen Details. 4.3.2 Kontursemantiken für Java Java hat ein paar spezielle Eigenheiten, die nicht bei allen objektorientierten Sprachen zu finden sind. JIVE muss allerdings auch diese speziellen Eigenschaften korrekt behandeln, um sie korrekt darstellen zu können: Statische Kontexte In Java gibt es die Notation von statischen Elementen, welche bei dem objektorientierten Standard-Kontur-Modell nicht vorgesehen ist. Variablen, Methoden und innere Klassen können als statisch deklariert werden, und diese statischen Elemente werden mit einer Klasse und nicht mit der Instanz einer Klasse assoziiert. Wenn nur eine statische Kontur zu einer Klasse existiert, so wird kein Instanzenzähler benötigt. Der Aufruf einer statischen Methode wird repräsentiert durch das Einsetzen einer Methoden-Kontur in die zugehörige statische Kontur. Samuel Strupp (349495)! 13 Visualisierung von Java-Programmen! 27.08.2005 Innere Klassen Eine innere Klasse ist eine Klasse, die im Kontext einer anderen Klasse definiert ist. Genauso werden anonyme innere Klassen verarbeitet und es wird das interne Nummerierungssystem von Java benutzt, um anonyme Klassen zu identifizieren. Nicht statische innere Klassen sind immer in genau einer umschliessenden Instanz enthalten und so wird in diesem Fall die Instanz-Kontur der inneren Klasse in die Instanz-Kontur der umschliessenden Instanz eingebettet. Nicht statische innere Klassen dürfen keine statischen Elemente definieren. Statische innere Klassen werden assoziiert mit dem statischen Klassen-Kontext und so wird die Instanz-Kontur einer statischen inneren Klasse in das Innere der statischen Kontur der umschliessenden Klasse eingebettet. Statische innere Klassen haben Abbildung 4.4: Kontur-Diagramm statische Elemente, und so hat jede statische innere mit nicht statischer innerer Klasse Klasse eine statische Kontur. Die statischen Konturen von statischen inneren Klassen brauchen keine spezielle Behandlung und werden wie jede andere statische Kontur behandelt. In Abbildung 4.4 sieht man ein Kontur-Diagramm bei dem die Klasse „BST2” eine nicht statische innere Klasse „Node” definiert. Diese innere Klasse instanziiert wiederum rekursiv eine weitere Instanz von „BST2”. Überschreiben und Überdecken Die Element-Tabelle jeder Instanz-Kontur besitzt in dem traditionellen Kontur-Modell für objekt-orientierte Sprachen zwei interne Verknüpfungen. Eine this (oder self)Verknüpfung, welche zu dem innersten Objekt eines Stapels von Konturen führt und einen super-Pointer, welcher auf die Kontur der Superklasse einer Kontur verweist, wenn eine existiert. Der Sinn von this ist das Überschreiben von Methoden und das Überdecken von Variablen zu zeigen. Eine this.print()-Anweisung wird immer die print()Methode, welche in der Klassenhierachie am tiefsten verankert ist, aufrufen. Es ist nicht möglich, die Methode oder Variable zu erhalten, die von this referenziert wird. Deshalb hat JIVE eine this-Referenz in der Element-Tabelle, die immer auf die innerste InstanzKontur zeigt, in einer Kollektion von eingebetteten Konturen. Die detaillierte Erklärung von Überschreiben und Überdecken wird so einem Dritten überlassen. Samuel Strupp (349495)! 14 Visualisierung von Java-Programmen! 27.08.2005 Threads Threads sind ein elementarer Bestandteil von Java. Mehrere gleichzeitige Threads implizieren viele simultane Pfade von Methodenaufrufen. Das ist ganz einfach im Konturenmodell repräsentiert mit visuellen Stichwörtern bei Methoden und ihren Return-Verknüpfungen. Jeder Threadpfad, der gerade ausgeführt wird, wird in einer anderen Farbe gezeichnet. Selbst wenn die selbe Methodendefinition von verschiedenen Threads aufgerufen wird, hat jeder Thread seinen eigene Methodenkontur, da jeder Thread seinen eigenen Stack besitzt. Um die verschiedenen Threads zu markieren, werden die selben Farben benutzt, sowohl im Kontur-Diagramm als auch im Sequenz-Diagramm als auch im Quellcode. Ein von JIVE erzeugtes Sequenzdiagramm ist in Abbildung 4.5 dargestellt. Das Programm startet Abbildung 4.5: Mehrere Threads einfach zwei Threads und lässt sie bis zur in einem Sequenzdiagramm Endbedingung laufen. 4.4 Die Architektur von JIVE Nachdem Gestwicki und Jayaraman in vorhergehenden Projekten versucht haben, Programmvisualisierungen durch Programm-Transformation darzustellen, haben sie diese Methode zugunsten einer Zwei-Prozess-Architektur aufgegeben. Der Grund hierfür liegt darin, dass bei jeder Weiterentwicklung von Java die selbstgeschriebenen Compiler und Interpreter auch wieder umgeschrieben werden müssen. Bei der in JIVE eingesetzten Zwei-Prozess-Architektur läuft die Visualisierungsumgebung selbst als ein Prozess. Der Benutzer gibt JIVE das Programm an, welches er visualisieren möchte, und JIVE startet das Programm in einem zweiten Prozess, dem so genannten „Client Prozess”. Die Kommunikation zwischen den beiden Prozessen wird durch die Java Plattform Debugger Architektur möglich gemacht (JPDA). Um den Quellcode anzeigen und markieren zu können, muss der Quellcode des Programms geladen werden, aber JIVE kann auch Visualisierungen von kompilierten Klassen erzeugen, solange diese noch DebuggerInformationen bereit halten. JIVE unterstützt Multithread-Programme, welche für den Einprozessor-Betrieb ausgelegt sind, aber das System unterstützt momentan keine verteilten oder Multiprozessor-Programme. Wenn JIVE erst einmal den Client-Prozess gestartet hat, registriert es Event-Listeners via JPDA und wartet auf Benachrichtigungen. Wenn sich der Clientzustand ändert, wird der Client-Prozess angehalten und die Benachrichtigung des Ereignisses wird an JIVE gesendet. Wenn das Datenmodell und die verschiedenen Ansichten aktualisiert wurden, kehrt JIVE wieder zum Client-Programm zurück und wartet auf neue Ereignisse. Die Menge von Schritten, die der Client ausführt, bevor er wieder angehalten wird, ist abhängig von den Benutzereinstellungen. JIVE erlaubt verschiedene Schrittgrößen, welche einzelne Quellcodezeilen, Methodenaufrufe oder traditionelle Breakpoints im Quellcode beinhalten. Es ist auch möglich, die Eventsperre zu deaktivieren, in diesem Fall werden die Ereignisse ununterbrochen an JIVE gesendet, welches diese dann sequenziell abarbeitet. Samuel Strupp (349495)! 15 Visualisierung von Java-Programmen! Client Prozess JPDA JIVE 27.08.2005 Programm Zustands Model Objekt Diagramm Ausführungs Protokoll Interaktives Interface Anfrage Interface Ausführungs Datenbank Programm Geschichts Model Sequenz Diagramm JIVE GUI Abbildung 4.6: JIVE Architektur. Die Boxen repräsentieren JIVE-Module und die Pfeile repräsentieren Daten- und Kontrollfluss Die Ablaufstruktur von JIVE ist wie folgt: Wenn das Client-Programm läuft, wird es auf Veränderungen überwacht. Die Änderungen werden in das Ausführungs-Protokoll aufgenommen, wie in Abbildung 4.6 dargestellt. Das Protokoll ist gekoppelt mit einer Datenbank, in der die Ausführungsgeschichte gespeichert wird. Ausführungs-Ereignisse, die vom Client empfangen werden, werden als einfachere Menge von Ereignissen von JIVE interpretiert. JIVE benutzt die folgenden Ereignisse: statische Kontext-Erzeugung, Objekt-Erzeugung, Methodenaufruf, Methodenreturn, ausgelöste und abgefangene Exceptions, Änderung in der Quellcodezeile, Änderung eines Variablenwertes. Jedes dieser Ereignisse ist gekapselt als ein Objekt, und jedes dieser Objekte enthält genug Informationen über das Ereignis, um entscheiden zu können, ob es sich selbst an das Ausführungs-Protokoll übergibt oder nicht. Im Wesentlichen enthält jedes Ereignis Informationen über die Änderungen, welches es an dem Ausführungszustand vornehmen wird. Werden in JIVE ältere Ausführungszustände betrachtet, hat dies keinen Effekt für das Client-Programm, sondern nur für die aktuellen Visualisierungen. Das Client-Programm wird angehalten, während der Benutzer vergangene Zustände begutachtet, und es wird erst wieder fortgesetzt, wenn es nötig ist. Da das Modell die Möglichkeit von sich ändernden Programm-eingaben verhindert, meidet es auch das Problem der beibehaltenen Synchronisation von externen Ressourcen, wie Datenströmen oder Ein-/ Ausgabe Geräten. Wenn zum Beispiel ein Programm mit einer grafischen Benutzeroberfläche in JIVE ausgeführt wird und der Benutzer in der Ausführungsgeschichte zurück geht, wird das Interface nicht reagieren, da der Client-Prozess angehalten wurde. Da JIVE auf Einprozessor-Systeme beschränkt wurde, gibt es auch immer nur eine Instruktion, die zu einer bestimmten Zeit aufgeführt wird. Deshalb können auch Multithread-Programme ohne Probleme visualisiert werden. JIVE weiß, welches Ereignis von welchem Thread kommt. Der aktive Thread ist im Ausführungsprotokoll verzeichnet und das wird in der Visualisierung reflektiert (siehe Abbildung 4.5). Es ist möglich, Kontur-Diagramme in Multigraphen zu konvertieren. Verschiedene Gewichte können zu den verschiedenen Typen von Verknüpfungen zugeordnet werden. Durch Experimente fanden Gestwicki und Jayaraman heraus, dass strukturelle Verknüpfungen permanenter als Methoden-Return-Verknüpfungen sind, und deshalb werden strukturelle Verknüpfungen höher gewichtet bei der Umwandlung in einen Multigraph. Der resultierende Graph wird dann unterteilt von einer modifizierten CoffmanSamuel Strupp (349495)! 16 Visualisierung von Java-Programmen! 27.08.2005 Graham Unterteilung [7] für Multigraphen und das Kreuzen wird minimiert [6]. Das Ergebnis ist eine unterteilte Graphenzeichnung. Eine „Inverse Transformation” wird angewandt, um aus dem Multigraph wieder das Kontur-Diagramm zu erhalten. Dies funktioniert sehr gut für einfache Strukturen, wie Bäume oder grafische Benutzeroberflächen-Kompositionen. JIVE unterstützt viele Ansichten mit vielen verschiedenen Detailstufen. Es gibt verschiedene Modelle, um Visualisierungen zu filtern. Zum Beispiel kann man einen Filter erzeugen, der alle Klassen von der Visualisierung ausnimmt, welche mit „java” beginnen. Die Geschichtsdatenbank speichert alle bisher aufgetretenen Ereignisse im Programm. So repräsentiert sie den kompletten Ablauf des Programms seit dem Programmstart. Das bietet den Vorteil, dass man im Ablauf des Programms interaktiv vor und zurück gehen kann. Die Speicherstruktur der Geschichtsdatenbank kann aufgefasst werden als eine objekt-orientierte Datenbank von Laufzeitzustandsinformationen. In dieser Laufzeitzustandsdatenbank werden Anfragen auf die Geschichte der Laufzeitausführung durchgeführt oder auf Teile davon und die Ergebnisse sind Werte, Mengen von Zuständen oder Teile der Programmgeschichte. So können Anfrage und Ergebnis visualisiert werden. Ein Grund für das Benutzen einer Laufzeitdatenbanken ist das Verfolgen von Variablenwerten. Eine Variable wird in JIVE ausgewählt und deren Wert wird auf Änderungen überwacht. Der Benutzer spezifiziert eine Bedingung für die Variable wie: Wert wurde geändert oder Wert liegt innerhalb einer bestimmten Wertemenge. Das Ergebnis der Variablenverfolgung wird in der JIVE-Benutzeroberfläche angezeigt. Die Geschichtsdatenbank erlaubt es auch, die Ausführungsdaten in eine Datei zu schreiben. Ein Programm-Geschichts-Datei kann wieder geladen und visualisiert werden. Diese Serialisierung der Ausführungsgeschichte macht es auch möglich, verschiedene Abläufe des selben Programms zu vergleichen. Wenn viele Ausführungsgeschichten existieren, können kleine Veränderungen im Ablauf unterschiedlicher Programmversionen visualisiert werden. 4.5 Kurzer Ausblick in die Zukunft Probleme gibt es bei JIVE vor allem noch bei der guten Darstellung von strukturierten Diagrammen. Wie kann man z.B. ein dynamisch wachsendes Sequenzdiagramm möglichst effektiv strukturieren? Auch die Struktur der Objektdiagramme kann sicher noch verbessert werden. Zudem wäre es wünschenswert, wenn man nicht nur Einzelschritte vorwärts und rückwärts gehen könnte, sondern auch von einem Zustand in einen weiter entfernten Zustand wechseln könnte, ohne dass alle dazwischen liegenden Zustände ausgeführt werden müssen. Das ist mit diesem Modell allerdings nicht möglich! Eine weitere Idee ist das Analysieren von Quellcodedateien, um Abhängigkeiten zwischen Methoden und Daten zu ermitteln und so für verschiedene Arten von Programmen die besten Ergebnisse zu liefern. Auch die Anfragen an die Geschichtsdatenbank können noch verbessert und erweitert werden. Die Daten aus der Geschichtsdatenbank könnten auch von anderen Programmen genutzt werden, um Programmanalysen durchzuführen. Zum Beispiel, wenn es um die Sicherheit geht. Wenn man ein Programm mehrmals laufen lässt, ist es vielleicht möglich, eine mathematische Beschreibung zu extrahieren, was ein „normaler” Ablauf ist. Verhält sich ein Programm dann extrem anders, kann es so als potenzielles Risiko eingestuft werden. Das Ergebnis eines solchen Vergleichs kann wiederum mit JIVE dargestellt werden. Samuel Strupp (349495)! 17 Visualisierung von Java-Programmen! 27.08.2005 5. JOVE 5.1 Über JOVE JOVE wird von Steven P. Reiss und Emmanuel Manos Renieris am „Department of Computer Science” an der Brown Universität entwickelt [2]. JOVE ist ein Java Visualisierungswerkzeug, welches Darstellungen eines Java Programms auf StatementEbene bietet, während dieses ausgeführt wird. JOVE profitierte bei der Entwicklung von dem vorhergehenden Projekt Namens JIVE, das ein anderes Visualisierungswerkzeug für Java Programme ist (dieses JIVE ist nicht das selbe JIVE, das von Gestwicki und Jayaraman entwickelt und in Kapitel 4 beschrieben wird). 5.2 Anforderungen an JOVE Das ältere System JIVE erfüllte bereits ein paar wesentliche Anforderungen: • Einen minimalen Overhead, so dass das System von jedem Programm benutzt werden konnte, und das zu jeder Zeit. • Das Maximale an gesammelten Daten darzustellen und zwar so, dass komplexe, miteinander agierende Muster erkennbar sind und somit auch das Verhalten besser zu verstehen ist. • Eine Ausführungsgeschichte bereitzustellen, damit der Benutzer sich den Ablauf des Programms noch einmal ansehen kann oder interessante Ausführungszustände inspizieren kann. • Die Darstellungen so anzeigen, dass verschiedene Arten von Verhalten des Programms, welche von besonderer Bedeutung sein könnten, besonders hervorgehoben werden, beispielsweise durch Farbe und Größe. • Dem Benutzer die Möglichkeit geben, die Darstellung an seine speziellen Probleme anzupassen. Allerdings gibt es auch ein paar Punkte, die JIVE nicht erfüllen konnte, welche aber von JOVE erfüllt werden sollen: • Informationen darüber, wo im Quellcode sich die Ausführung gerade befindet, um feststellen zu können, in welchen Bereichen das Programm viel Zeit verbringt. • Informationen darüber, welche Instruktionsausführungen mit welchem Thread in Verbindung stehen, so dass nachvollzogen werden kann, was ein Thread gerade macht und nicht nur, in welchem Zustand er sich befindet. Alle diese Punkte sind Anforderungen, die JOVE erfüllen soll. Das Konzept für JOVE konzentriert sich also vor allem auf die Datensammlung über den Programmablauf, welche zudem threadspeziefisch aufgeschlüsselt wird. 5.3 Lösungen in JOVE JOVE unterteilt die subjektive Programmausführung in Intervalle von etwa 10 Millisekunden. In jedem Intervall registriert JOVE, was das Programm in jedem seiner Samuel Strupp (349495)! 18 Visualisierung von Java-Programmen! 27.08.2005 Threads macht. Um dies realisieren zu können, benutzt JOVE so genannte Basisblöcke, um spätere Rechen- und Performance-Analysen durchführen zu können. Ein Basisblock ist ein Segment von reinem Zeilencode ohne interne Verzweigungen. Das bedeutet, wenn solch ein Block gestartet wird, dann werden auch alle Instruktionen des Blocks ausgeführt. Man kann jetzt zählen, wie oft einzelne Instruktionen ausgeführt werden, indem man einfach zählt, wie oft der zugehörige Block durchlaufen wurde. Aus diesen Informationen und aus den Informationen vom Debugger, welche Ausdrücken, welche Instruktionen welchen Zeilen im Code entsprechen, können dann die Basisblock-Zählungen benutzen werden, um die Häufigkeit der Ausführung bestimmter Zeilen und deren Anzahl der Maschinen-Instruktionen pro Zeile zu ermitteln. Die Messwerte, die JOVE registriert, sind Tupel der Form <I,T,B,c>, wobei I das Intervall identifiziert, T den Thread, B den Basisblock und c die Anzahl der Ausführungen des Basisblocks B durch den Thread T im Intervall I. JOVE erweitert die Basisblock Zählungen um Zählungen von Allokationen und ausgeführten Instruktionen durch eine statische Analyse der Basisblöcke. Es ordnet diese Statistiken auf verschiedene Weisen, zum Beispiel nach Datei oder nach Thread und bildet die Ergebnisse auf visuelle Elemente in einer informationsverdichteten Darstellung ab. Abbildung 5.1: Beispiel einer JOVE-Visualisierung Das Visualisierungsfenster von JOVE ist in mehrere vertikale Regionen unterteilt. Jede Region entspricht dabei einer Datei des zu visualisierenden Systems. Jede Dateivisualisierung ist wieder unterteilt in einen oberen Teil, in welchem die Threadinformationen dargestellt werden, und in einen unteren Teil für die Blockinformationen (siehe Abbildung 5.1). In der unteren Region ist ein horizontales Rechteck für jeden Basisblock. Diese Rechtecke sind geordnet nach der Nummer der entsprechenden Quellcodezeilen. Diese zwei Regionen der Darstellung müssen dynamisch geändert werden können entsprechend den gesammelten Daten des kompletten letzten Intervalls. Das Ergebnis ist dementsprechend eine Animation des laufenden Programms. Der größte Teil der Visualisierung bezieht sich immer auf das letzte Intervall. Die Breite der entsprechenden Dateispalte reflektiert allerdings den kompletten Ablauf für diese Datei bis zum momentanen Intervall und entspricht dem prozentualen Anteil der Anzahl von Instruktionen, welche in dieser Datei über den gesamten Ablauf ausgeführt wurden. Der obere Teil jeder Dateidarstellung enthält ein Kreisdiagramm. Das Innere des Kreisdiagramms stellt die gesamte Ablaufzeit des momentanen Intervalls innerhalb der Samuel Strupp (349495)! 19 Visualisierung von Java-Programmen! 27.08.2005 Datei dar. Das Kreisdiagramm ist in Sektoren aufgeteilt. Jeder Sektor entspricht dabei der prozentual verbrachten Zeit in einem bestimmten Thread. Die Standardhintergrundfarbe für jede vertikale Region kodiert drei verschiedene Statistiken. Der Farbton stellt die Anzahl der Instruktionen, die im letzten Intervall ausgeführt wurden, über ein grün zu gelb Farbmodell da. Die Sättigung gibt die Anzahl der Ausführenden Threads im aktuellen Intervall wieder. Und die Helligkeit die Anzahl der Allokationen, die von Blöcken in dieser Datei im aktuellen Intervall gemacht wurden. Wenn kein Code in der Datei während dem Intervall ausgeführt wurde, wird der Hintergrund grau dargestellt. Die Basisblöcke einer Datei werden jeweils im unteren Teil der Dateiregion durch ein horizontales Rechteck dargestellt. Die Höhe des Rechtecks entspricht der Anzahl von Quellcodezeilen. Die Breite des Rechtecks korrespondiert zu der Anzahl von Instruktionen, die in dem Basisblock im letzten Intervall ausgeführt wurden. Die Linie ist aufgeteilt in viele farbige Regionen, bei der jede einem Thread entspricht. Die Farbgebung entspricht dabei der des Kreisdiagramms. Abbildung 5.2: Dialogboxen um JOVE zu konfigurieren Jeder Benutzer kann JOVE so einstellen, dass er eine geeignete Ansicht seiner Applikation bekommt. Innerhalb von JOVE kann der Benutzer Dialogboxen benutzen, welche in Abbildung 5.2 gezeigt sind, um die verschiedenen Kommandozeilenargumente einzustellen, um die Intervallgrößen zu ändern, um Klassen zu definieren, welche dynamisch geladen werden und nicht statisch auffindbar sind, aber überwacht werden sollen, und um festzulegen, welche Pakete und Klassen überwacht werden sollen und welche ignoriert werden sollen. Der Benutzer kann auch kontrollieren, welche Statistiken angezeigt werden sollen und auf welche Weise (Abbildung 5.2, rechte Dialogbox). Er kann auch zwischen totalen oder sofortigen Werten wählen für alle statistischen Eigenschaften. Desweiteren kann der Benutzer zwischen linearer, Quadratwurzel- oder logarithmischer Skalierung wählen, um die verschiedenen Aufteilungen dieser Werte mit diesen Eigenschaften zu betrachten. Zudem sind weiter Darstellungsdetails, wie z.B. Farbmodelle veränderbar. 5.4 Die Architektur von JOVE Die Kontrollkomponente von JOVE stellt dem Benutzer ein einfaches Interface zu Verfügung. Dort kann der Benutzer definieren, welches Programm überwacht werden soll, Argumente können eingestellt oder geändert werden, die sich auf den Java-Interpreter Samuel Strupp (349495)! 20 Visualisierung von Java-Programmen! 27.08.2005 oder den Benutzercode beziehen. Zudem ist es möglich, einzustellen, welche Klassen oder Pakete überwacht und welche ignoriert werden sollen. Die Aufbaukomponente ist ein unabhängiger Prozess, welche alle Klassen, die überwacht werden sollen, identifiziert und welche diesen Klassendateien den benötigten Code für die Überwachung hinzufügt. Dieser „Patcher” benutzt das IBM JikesBT byte code Toolkit [5]. Diese Komponente produziert zwei Ausgaben. Die erste ist eine jar-Datei, welche die modifizierten Klassen beinhaltet. Dieses wird benutzt, um die originalen Klassendateien zu ersetzen, wenn das Programm abläuft. Die zweite Ausgabe ist eine Beschreibungsdatei, welche Informationen über jeden Basisblock auflistet: zu welcher Methode und Klasse der Block gehört, die entsprechende Quellcodezeile, die Anzahl der Instruktionen und Allokationen in diesem Block und die Typen der Objekte, die von den Allokationen erzeugt werden. Die zweite Datei wird vom Visualisierer benutzt, die Zähldaten der Basisblöcke in aussagekräftigere Informationen für den Benutzer umzuwandeln. Die Information-Sammlungs-Komponente ist eine kleine Bibliothek, die mit dem Benutzerprogramm geladen wird. Die Bibliothek wird zu Beginn von dem veränderten Code aufgerufen. Sie ist zuständig für die Zählungen und das dynamische zur Verfügung stellen der passenden Daten für den Visualisierer. Der Schlüssel zu einem erfolgreichen dynamischen Echt-Zeit-Visualisierungssystem ist Sammeln der angemessenen Verfolgungsdaten mit einem minimalen Overhead. Für JOVE bedeutet das zu zählen, wie oft jeder Basisblock in jedem Thread in einem Intervall ausgeführt wurde. Es ist kein Nachteil, Informationen auf Basisblockebene statt auf Methodenebene zu sammeln. Auf Methodenebene muss nicht nur jeder Methodeneintritt und -austritt verändert werden, sondern es müssen auch Allokationen oder unnormale Austritte durch Exceptions beachten werden. Diese Erfordernisse müssen auf der Basisblockebeneverfolgung nicht berücksichtigt werden, da Informationen über Allokationen in den Basisblock Informationen enthalten sind, da davon ausgegangen werden kann, dass in fast allen Fällen feststeht, welche Objekte alloziert werden, wenn ein Block ausgeführt wird. Zweitens, falls ein Block einen Exception-Handler repräsentiert, kann diese Exception über die Ausgabesequenz festgestellt werden. Ein Problem ist, wie man an Basisblockinformationen kommt, in denen die Zählungen mit den aktuellen Thread assoziiert werden. Die Lösung des Problems ist das Hinzufügen eines zusätzlichen Parameters zu jeder Routine, welcher den aktuellen Kontext beinhaltet, welcher wiederum den aktuellen Thread und die Zählinformationen für diesen Thread beinhaltet. Reiss und Renieris realisierten dies durch das Erzeugen einer Schatten-Routine für jede Routine, die überwacht werden soll. Der Name der Original-Routine wird geändert, der KontextParameter hinzugefügt genauso wie der Code, welcher die entsprechende Methode für den Basisblockeintritt aufruft. Dann wird eine neue Routine mit dem Original-Namen und Parametern erzeugt. Die neue Routine errechnet den Kontext (indem sie den aktuellen Thread findet und dann den Kontext bei diesem nachsieht) und ruft danach die modifizierte Original-Routine auf. Alle Aufrufe von Routinen des veränderten Codes werden dann noch so verändert, dass die korrespondierenden umbenannten Routinen aufgerufen werden. Diese Lösung bedeutet, dass beinahe alle Aufrufe, die im modifizierten Code gemacht werden, die modifizierten Original-Routinen benutzen und nicht einen neuen Kontext berechnen müssen. Außerdem können korrekte Aufrufe von unmodifiziertem Code und Routinen, welche Interfaces oder abstrakte Methoden implementieren behandelt werden. Um Blöcke in einem Thread effizient zählen zu können, braucht man den Kontext von jedem Thread vorallokierte Zählpuffer für Zählungen jedes enthalten Basisblocks in der überwachten Quelle. Der Code beim Basisblockeintritt Samuel Strupp (349495)! 21 Visualisierung von Java-Programmen! 27.08.2005 inkrementiert einfach den entsprechenden Zähler für den aktuellen Kontext. Ein Kontexttriple puffert diese Zählungen. Der erste Puffer enthält die aktuellen Zählungen. Ein zweiter Puffer wird benutzt, um die Daten zu halten, welche momentan verarbeitet werden, um an das Visualisierungs-Front-End gesendet zu werden. Ein dritter Puffer, welcher das vorhergehende Intervall repräsentiert, ist verfügbar, um Vergleiche mit dem zweiten durchführen zu können. Die aktuelle Sammlung und das Melden der Daten zu dem Visualisierungs-Front-End wird durch einen seperaten Thread erledigt, welcher einen Timer ablaufen lässt, der wiederum die Intervalle errechnet. Dieser Thread behandelt auch das Puffer-Swapping, produziert eine XML-Nachricht, welche die nicht Null Zählungen zusammen fasst und sendet diese Nachricht über ein Socket zum Visualisierer. Die Visualisierungskomponente stellt zwei Ansichten zur Verfügung. Eine ist eine Mitschrift der Ein- und Ausgaben des Programms. Die zweite ist die Hauptansicht der gesammelten Informationen wie in 5.3 beschrieben. Diese Komponente ist zuständig für das Speichern und Zugreifen auf die dynamischen Informationen, für das Erzeugen von angemessenen Ansichten, die auf den Benutzereinstellungen basieren, für das dynamische Aktualisieren dieser Ansicht, während das Programm abläuft, und für das Bereitstellen eines zeitbasierten Zugriffs auf die Daten. Informationssammlungen ergeben Basisblock Zählungen für jedes Intervall für jeden Thread oder Kontext. Für die Darstellung werden diese Informationen nach dem Kontext und global für jede Quelldatei gesammelt. Wie werden die Informationen jetzt effizient dargestellt? Wie schon erwähnt gibt es 3 Basistypen an Informationen, welche dargestellt werden können: 1. Informationen über die individuellen Basisblöcke und daraus resultierende Quellzeilen: ! - Anzahl der Ausführungen!! ! ! ! für jeden individuellen Thread ! - Anzahl der gemachten Allokationen ! ! für jeden individuellen Thread ! - Anzahl der ausgeführten ByteCodes! ! für jeden individuellen Thread ! - Anzahl der Ausführungen!! ! ! ! für alle Threads zusammen ! - Anzahl der gemachten Allokationen ! ! für alle Threads zusammen ! - Anzahl der ausgeführten ByteCodes! ! für alle Threads zusammen 2. Informationen über Dateien: ! - totale Zählungen der verschiedenen Statistiken von allen Blöcken ! ! ! ! ! ! ! ! ! ! für jeden individuellen Thread ! - totale Zählungen der verschiedenen Statistiken von allen Blöcken ! ! ! ! ! ! ! ! ! ! für alle Threads zusammen 3. verschiedene Statistiken geordnet nach Threads Alle drei Informationstypen sollen in der selben Ansicht dargestellt werden. Dabei muss sichergestellt sein, dass so viele Daten wie möglich dargestellt werden, und dass interessante Werte, besondere mögliche Performanceprobleme, Threadkonflikte und interessante Veränderungen im Verhalten hervorstechen. Wie in Kapitel 5.3 beschrieben ist die Ansicht in vertikalen Dateiregionen organisiert, bei der jede ein Kreisdiagramm für Kontexte beinhaltet, und eine Ansicht der Basisblöcke. Globale Dateiinformationen werden durch die Datei-Regionsgröße, Hintergrundfarbe, und den Bereich zwischen dunklem und hellen Hintergrund reflektiert. Die Rolle der Kontexte in der Datei innerhalb eines Intervalls wird durch das Kreisdiagramm wiedergespiegelt. Rechtecke in der Basisblockregion reflektieren den Kontext, Allokationen und die Anzahl der Instruktionen, welche während dem Intervall ausgeführt wurden. Das Entscheidende bei der JOVE-Darstellung ist die Möglichkeit, viele Dimensionen der Daten auf einem sinnvollen Weg darzustellen. Die Samuel Strupp (349495)! 22 Visualisierung von Java-Programmen! 27.08.2005 normale Dateiansicht zeigt fünf verschiedene Statistiken und benutzt dabei die Breite, den Farbton, die Sättigung, die Helligkeit und die Position von den Unterteilungslinien. Das Kreisdiagramm zeigt die verschiedenen Kontexte durch Farben und kann durch die Kreisabschnittsgröße verschiedene Statistiken über den Kontext reflektieren (z.B. Allokationen und Instruktionen). Die Basisblöcke können bis zu fünf Statistiken enthalten mit ihrer Breite, Höhe, Farbton, Sättigung und Helligkeit. Da die Ansicht eine Menge an Informationen zu Verfügung stellt, muss der Programmierer sie mit dem Ursprungsprogramm in Verbindung setzen können. Anstatt den Daten Bezeichnungen zu geben (wo der Text oft zu klein zum Lesen wäre), werden Tooltips benutzt welche dem Benutzer Abbildung 5.3: Tooltip in JOVE detaillierte Informationen zur Verfügung zu stellen. Abblidung 5.3 zeigt einen solchen Tooltip, welcher angezeigt wird, wenn man die Maus über einer Fläche der Visualisierung platziert. Datenstruktur Bei einem lange laufenden Programm kann eine große Anzahl von Intervallen existieren, wobei jedes signifikante Daten enthält. Zum Beispiel in einem relativ einfachen Spiel existieren etwa 3000 Basisblöcke, die verfolgt werden müssen für jedes Intervall und für jeden der vierzehn aktiven Threads. Des weiteren wird die Möglichkeit des Zugriffs auf die zusammenfassenden Zähler für Dateien und Threads benötigt. Dies ist nicht einfach, da unterschiedliche Statistiken separat gesammelt werden müssen (Allokationen, Instruktionen und Blockzählungen), da diese nicht von den rohen Zählimpulsen direkt abgeleitet werden können, wie es für die BasisIntervalle: blöcke gemacht werden kann. Um diese Daten zu speichern, haben Reiss und Renieris einen Vektor Lokal Global von Intervallen kreiert. Jedes Intervall wird Block 1 Block 1 repräsentiert durch zwei Hash-Tabellen, eine für Block 2 Block 2 Block n Block n die lokalen Daten des Intervalls und eine für die Datei 1 Datei 1 Datei 2 Datei 2 totalen Daten bis und einschliesslich diesem Total Total Intervall. Jede Hash-Tabelle ist indiziert durch den Block i Block i Block oder Datei, wo der assoziierte Wert ein Thread 1 Thread 1 Thread 2 Thread 2 Statistiken Statistiken andere Hash-Tabelle ist. Für jedes individuelle <#Blöcke> <#Blöcke> Total Total <#Instanzen> <#Instanzen> Objekt, bildet diese zweite Hash-Tabelle den <#Allokationen> <#Allokationen> Kontext (Thread) auf einen Block von Abbildung 5.4: Datenstruktur um die Zähler-Informationen zu speichern Statistischen Werten, welche die verschiedenen Zählungen repräsentieren. Ein spezieller Kontextwert wird benutzt, um den globalen Kontext zu repräsentieren. Dies alles ist in Abbildung 5.4 dargestellt. Alle Zählungen des Informationssammlers auf Blockebene werden zuerst in die lokalen Tabellen eingetragen. Jede Zählung ungleich Null generiert einen Statistikeintrag für den entsprechenden Block und Thread (auf beiden Ebenen der Hash-Tabelle). Die Totalzählung für alle Threads wird als separate Statistik berechnet von den lokalen Totalen. Genauso wird jede Blockzählung in die entsprechenden Einträge für die Datei eingetragen, welche in der ersten HashTabelle indiziert wurde. Zuletzt werden die Totalen von jedem Eintrag berechnet. Das Ergebnis ist eine komplettes Set von Statistiken ungleich Null für jedes Intervall. Dieses kann schnell indiziert werden, nach Block, Datei oder Thread. Für jeden Eintrag in den vorhergehenden Totalen die keinen entsprechenden lokalen Eintrag haben, wird einfach auf den vorhergehenden Block verwiesen. Andernfalls wird ein neuer Eintrag erzeugt Samuel Strupp (349495)! 23 Visualisierung von Java-Programmen! 27.08.2005 welcher die Summe von jedem vorhergehenden totalen Eintrag und die neuen lokalen Werte beinhaltet. Durch diese Methoden entsteht ein speichereffizienter Lagermechanismus, welcher zügigen Zugriff auf alle verfügbaren Statistiken bietet und die Tatsache in Betracht zieht, dass die meisten Zählungen Null ergeben. 5.5 Kurzer Ausblick in die Zukunft Mit JOVE braucht ein Programm etwa drei bis viermal so viel Zeit wie normal. Eigentlich ist diese Verlangsamung akzeptabel, aber es kann vielleicht noch optimiert werden. Ein weiterer Punkt ist das Erheben von noch mehr Daten eines Programmdurchlaufs, was dann aber natürlich eine weitere Verlangsamung mit sich bringt. Oder das Beobachten des Speichers. In Java ist es relativ einfach, Allokationen zu registrieren, aber Deallokationen, die letztlich vom Garbage Collector durchgeführt werden, sind viel schwieriger. Und noch ein interessanter Punkt ist das Assoziieren des Programmverhaltens mit Ereignissen wie Eingaben oder generierten Ereignissen. So könnte man bestimmen, welche Prozesse ausgeführt werden bei einer bestimmten Eingabe. 6. Zusammenfassung und eigene Meinung Die beiden vorgestellten Programme „JIVE” und „JOVE” sind also Javaprogrammvisualisierungswerkzeuge, welche ein paar der vorgestellten Ideen aus Abschnitt 3 umsetzen (Quellcode, Objektstrukturen, Instanzen, ...). Die Anforderungen, die an das jeweilige Projekt gestellt wurden, sind im Wesentlichen erfüllt worden. JIVE eignet sich, um in der Lehre die Struktur und Funktionsweise von objektorientierten Sprachen zu vermitteln. Es ist auch durchaus als visueller Debugger einsetzbar. JOVE erreicht auch sein Ziel, langsamen Quellcode zu identifizieren. Allerdings sind beide Werkzeuge nur für kleine, nicht so komplexe Programme geeignet, da schnell die Skalierbarkeit der Darstellung sowie der Datenerhebung und Datenverarbeitung erreicht ist und das jeweilige Programm so nicht mehr benutzbar wird. Ein weiteres Manko von Seiten der Entwickler ist, dass beide Programme im Moment nicht in der aktuellen Version im Netz verfügbar sind. Samuel Strupp (349495)! 24 Visualisierung von Java-Programmen! 27.08.2005 7. Literaturangaben [1] ! Paul Gestwicki, Bharat Jayaraman ! „Methodology and Architecture of JIVE” ! ACM symposium on Software visualization (2005) [2]! ! ! Steven P. Reiss, Emmanuel Manos Renieris „JOVE: Java as it Happens” ACM symposium on Software visualization (2005) [3]! ! ! Katharina Mehner „JaVis: A UML-Based Visualization and Debugging Environment for Concurrent Java Programs” (2001) [4]! ! Wim De Pauw, Erik Jensen, Nick Mitchell, Gary Sevitsky, John Vlissides, Jeaha Yang „Visualizing the Execution of Java Programs” (2001) [5]! ! Chris Laffra, Doug Lorch, Dave Streeter, FrankTip, John Field „What is Jikes Bytecode Toolkit” http://www.alphaworks.ibm.com/tech/jikesbt (2000) [6]! ! P. Eades, G. Di Battista, R. Tamassia, and I. G. Tollis. „Graph Drawing: Algorithms for !the Visualisation of Graphs” (1999) [7]! ! E. G. Coffman, Jr. und R. L. Graham „Optimal scheduling for two-processor systems.” Acta Informatica (1972) [8]! ! ! Blaine Price, Ronald Baecker, Ian Small „An Introduction to Software Visualization” Software Visualization – Programming as a Multimedia Experience (1998) [9]! ! Azureus - Java BitTorrent Client http://azureus.sourceforge.net Hilfreiche Seiten waren außerdem: http://www.software-kompetenz.de http://www.wikipedia.de Samuel Strupp (349495)! 25