-
Diese
Erfindung betrifft die Programmanalyse und die Optimierung von Java-Programmmodulen.
-
Die
Laufzeit von Programmen lässt
sich beträchtlich
verringern, wenn man Optimierungen am Code und am Compiler vornimmt,
so dass Ressourcen besser ausgenutzt werden. Bei Programmen, die viele
kurze Prozeduren enthalten, wie es bei vielen objektorientierten
Programmen der Fall ist, sind Optimierungen zwischen den Prozeduren
notwendig, damit man einen leistungsfähigen Code erhält. Die wichtigste
Optimierung zwischen Prozeduren ist das Inlining (Ersetzen des Funktionsaufrufs
durch den Funktionsrumpf, der direkt eingefügt wird), das die Leistungsfähigkeit
durch Verringerung des durch Aufrufe verursachten Zusatzaufwands
und durch Erhöhung
der Optimierungschancen verbessern kann. Optimierungen zwischen
Prozeduren sind jedoch durch die virtuellen Aufrufe von objektorientierten Programmen
begrenzt. Die Fähigkeit,
die möglichen Ziele
eines virtuellen Aufrufs festzustellen, die häufig als "Aufruf-Devirtualisierung" bezeichnet wird,
ist für solche
Programme folglich von entscheidender Bedeutung und hat in den letzten
Jahren viel Beachtung gefunden.
-
Die
Programmiersprache und Laufzeitumgebung Java stellen für die Devirtualisierung
neue Herausforderungen dar. Tatsächlich
kann es sogar unmöglich
sein, im Voraus alle möglichen
Ziele eines virtuellen Aufrufs in Java festzustellen. Neue Klassen,
die zusätzliche
unvorhergesehene Ziele enthalten, könnten jederzeit geladen werden.
Eine komplexe Analyse ist gegebenenfalls notwendig, um vollständig alle
möglichen
Ziele eines virtuellen Aufrufs festzustellen. Die bestehende Unsicherheit
darüber, welches
die möglichen
Ziele sind, schränkt
die Möglichkeit
der Durchführung
von Optimierungen zwischen Prozeduren erheblich ein. Solche Optimierungen
müssen
sich entweder darauf verlassen, dass der Benutzer alle Klassen und
Schnittstellen angibt, die dynamisch geladen werden können, oder
sie müssen
wiederholt prüfen,
welches das tatsächliche Ziel
ist und Standardmaßnahmen
für unerwartete Ziele
treffen. Andernfalls besteht das Risiko, dass die Optimierung nicht
mehr aktuell ist, wobei in diesem Fall ein spezieller Mechanismus
notwendig ist, um die Optimierung rückgängig zu machen. Ein solcher Mechanismus
ist in "The JavaTM HotSpotTM Virtual Machine
Architecture" von
David Griswold, veröffentlicht
von Sun Microsystems, Inc., März
1998, beschrieben, das unter http://java.sun.com:81/products/hotspot/whitepaper.html
als Weißbuch
erhältlich
ist.
-
Bei
herkömmlichen
objektorientierten Sprachen wie C++ lässt sich die Gruppe der möglichen Ziele
eines virtuellen Aufrufs leicht feststellen oder zumindest näherungsweise
vorsichtig ermitteln. Dies ist deshalb so, weil alle Klassen, die
von dem Programm möglicherweise
verwendet werden können, festgelegt
und zum Zeitpunkt der Kompilierung bekannt sind. Ein Aufruf einer
bestimmten Methode hat möglicherweise
irgendeine überschreibende
Implementierung dieser Methode zum Ziel. Somit erfolgt eine einfache
näherungsweise
Ermittlung der Ziele eines Aufrufs über die Gruppe aller überschreibenden Implementierungen
der aufgerufenen Methode. Diese Gruppe kann schnell erstellt werden,
indem der Vererbungsbaum, der die aufgerufene Klasse als Wurzel
hat, durchsucht wird. Verschiedene Verfahren wurden in den letzten
Jahren entwickelt, um die Gruppe der möglichen Ziele eines virtuellen
Aufrufs zu verfeinern, wobei Untergruppen der Gruppe aller überschreibenden
Implementierungen gebildet wurden. Es ist bekannt, eine Analyse
des Lebendigkeitsanteils (liveness analysis) durchzuführen, um
auszuschließen,
dass Implementierungen ungenutzter Methoden ("dead method implementations"), die während der
Ausführung
des Programms garantiert nie aufgerufen werden, mögliche Zielkandidaten
werden. All diese Verfahren beruhen darauf, dass sie alle wichtigen
Klassen, die zur Laufzeit möglicherweise referenziert
werden, zum Zeitpunkt der Analyse festgelegt und verfügbar haben.
-
Bei
Java-Programmen ist es im Allgemeinen jedoch unmöglich, im voraus zu wissen,
welche Klassen tatsächlich
beteiligt sein werden. Die jeweilige Klassendatei wird nur zur Laufzeit
aufgefunden und geladen, wenn die Klasse erstmalig referenziert
wird. Bis zu diesem Zeitpunkt kann sich eine Änderung, die an den Klassendateien
selbst oder an anderen Parametern (wie zum Beispiel der Umgebungsvariablen CLASSPATH)
vorgenommen wurde, auf die Klassen auswirken, die geladen werden.
Dieses dynamische Verhalten schränkt
die Möglichkeit,
eine Analyse zwischen Klassen durchzuführen, und insbesondere die Möglichkeit,
die möglichen
Ziele von virtuellen Aufrufen festzustellen, ein. Es gibt einen
weiteren Unterschied zwischen Java und anderen objektorientierten Sprachen,
was die möglichen
Ziele von virtuellen Aufrufen angeht. Bei Java (im Gegensatz zu
anderen Sprachen) ist es möglich,
dass ein virtueller Aufruf eine überschreibende
Implementierung erreicht, deren Klasse die aufgerufene Schnittstelle
nicht implementiert. Dennoch besteht die Möglichkeit, alle überschreibenden
Implementierungen einer Java-Methode zu finden, indem die Unterklassen
der Klasse oder der Schnittstelle, die die Methode enthält, durchsucht und
die Oberklassen dieser Unterklassen hin und wieder geprüft werden.
-
Derzeit
folgt man zwei Hauptansätzen,
um diesem dynamischen Verhalten von Java gerecht zu werden und dennoch
eine Analyse und Optimierung zwischen Klassen zu ermöglichen.
Ein Ansatz besteht in der Annahme, dass alle wichtigen Klassen vom
Benutzer geliefert werden, und dem Zurückgreifen auf herkömmliche
statische Verfahren. Ein solcher Ansatz wurde von Instantiations,
Inc. in ihrem Programm JOVETM (http://www.instantiations.com/jove/jovereport.htm) übernommen,
das ganze "versiegelte" Java-Anwendungen
auswertet und kompiliert.
-
Der
große
Nachteil dieses Ansatzes besteht darin, dass er sich nicht auf (dynamische)
Anwendungen anwenden lässt,
die unvorhergesehene Klassen oder Schnittstellen zur Laufzeit dynamisch laden
können.
-
Der
zweite Ansatz besteht in der Annahme, dass neue Klassen (zur Laufzeit)
erscheinen können, und
in der Ausarbeitung eines Mechanismus, mit dem sich nicht mehr aktuelle
Analysen erkennen und Optimierungen, die auf diesen Analysen beruhten, rückgängig machen
lassen. Es sei angemerkt, dass es gewöhnlich unmöglich ist, eine Optimierung "rückgängig zu machen", nachdem der optimierte
Code eingegeben worden ist, so dass folglich eine Standardmaßnahme für den Umgang
mit unvorhergesehenen Zielen vorgesehen werden muss, wenn Code am
Ort des Aufrufs direkt in bestimmte Methoden eingesetzt wird. Ein
solcher Ansatz wird von dem vorstehend in Bezug genommenen Hotspot
verwendet. Der große
Nachteil dieses Ansatzes besteht in dem Mehraufwand zur Laufzeit,
der durch diesen zusätzlichen
Mechanismus und durch die gelegentlich auftretende Notwendigkeit,
unvorhergesehene Ziele zu erkennen und sich mit ihnen zu befassen,
verursacht wird.
-
In "Optimization of Object-Oriented
Programs Using Static Class Hierarchy Analysis" (XP000856072) beschreiben J. Dean u.a.
die Optimierung von Compilern für
objektorientierte Sprachen, die eine statische Klassenanalyse und
andere Verfahren anwenden, um zu versuchen, präzise Informationen über die
möglichen
Klassen der Empfänger
von Nachrichten abzuleiten; im Erfolgsfall können dynamisch versandte Nachrichten
durch direkte Prozeduraufrufe ersetzt und möglicherweise durch eine Inline-Erweiterung
weiter optimiert werden. Indem man den vollständigen Vererbungsgraphen eines Programms
prüft,
was wir Klassenhierarchie-Analyse ("class hierarchy analysis") nennen, kann der
Compiler die Qualität
von statischen Klasseninformationen verbessern und dadurch die Leistungsfähigkeit
zur Laufzeit verbessern. In dieser Schrift legen wir die Klassenhierarchie-Analyse
dar und beschreiben Verfahren zur wirksamen Durchführung dieser
Analyse in Sprachen sowohl des statischen als auch des dynamischen
Typs und auch in Anwesenheit von Mehrfachmethoden. Wir erörtern auch,
wie die Klassenhierarchie-Analyse in einer interaktiven Programmierumgebung
und bis zu einem gewissen Grad bei einer getrennten Kompilierung
unterstützt
werden kann. Schließlich
bewerten wir die Leistungsverbesserung, die durch die Klassenhierarchie-Analyse
allein und die Klassenhierarchie- Analyse
in Verbindung mit zwei anderen "konkurrierenden" Optimierungen, der
profilorientierten Vorhersage der Empfängerklasse und der Methodenspezialisierung,
im Endeffekt erzielt wurde.
-
In "Sharpening Global
Static Analysis to Cope with Java" (XP002263002) beschreiben S. Porat
u.a. ein Java-Paket, JAN, das statische Informationen einer Java-Anwendung
erfasst und bearbeitet. Der Schwerpunkt von JAN (Java Analysis)
liegt in der Bereitstellung eines wirksamen Verfahrens zum Aufbau
des Aufrufgraphen einer bestimmen Java-Anwendung. Diese Ansicht
kann zum Verständnis
des Programms und auch zu Optimierungszwecken wie zum Beispiel der
Verringerung der Code-Größe und der
Devirtualisierung genutzt werden. JAN erweitert Verfahren, die für andere
objektorientierte Sprachen entwickelt wurden. Hauptsächlich trägt JAN dazu
bei, den speziellen Funktionen von Java Rechnung zu tragen. Wir
führen
den Begriff der Teilanalyse ein, indem wir zwischen einer zu analysierenden
Gruppe von Klassen und einer Gruppe von Klassen, die vorverarbeitet
werden, unterscheiden. Dies ist bei Java, wo ein hoher Prozentsatz
des Anwendungscodes zu Bibliotheksklassen gehört, die vom Benutzercode unabhängig sind, äußerst hilfreich.
JAN wurde so erweitert, dass es eine Analyse der Hierarchie und
den Aufbau eines Aufrufgraphen für
(auf RMI basierende) verteilte Java-Anwendungen unterstützt.
-
Vorzugsweise
würde eine
solche Nachkompilierung vermieden werden, indem sichergestellt wird,
dass die eingangs erfolgte Optimierung in sich abgeschlossen ist
und folglich zur Laufzeit nicht veraltet sein wird. Dies lässt sich
jedoch nur erreichen, wenn bestimmte Aufrufe ordnungsgemäß als "versiegelte Aufrufe" gekennzeichnet werden
können,
deren Gruppe möglicher
Ziele sogar noch vor der Laufzeit vollständig und eindeutig festgestellt
werden können.
-
Es
ist daher eine Aufgabe der Erfindung, ein Verfahren bereitzustellen,
das dazu dient, in Java-Paketen Aufrufe zu kennzeichnen, bei deren
Zielen gewährleistet
ist, dass sie zu demselben Paket gehören.
-
Erfindungsgemäß wird ein
Verfahren zur Kennzeichnung eines Aufrufs in einem versiegelten Java-Paket
bereitgestellt, bei dessen Ziel gewährleistet ist, dass es zu dem
Paket gehört,
wobei das Verfahren die folgenden Schritte umfasst:
- (A) Bestätigen,
dass das Paket versiegelt und signiert ist,
- (B) Abrufen eines Klassenhierarchiegraphen für Klassen und Schnittstellen
des Pakets,
- (C) Abrufen von Zugriffsberechtigungen von einzelnen Komponenten
in dem Paket und
- (D) als Reaktion auf die Bestätigung, dass das Paket versiegelt
und signiert ist, Kennzeichnen eines Aufrufs unter Verwendung des
Klassenhierarchiegraphen und von Zugriffsberechtigungen, um festzustellen,
ob bei dem Ziel des Aufrufs gewährleistet
ist, dass es zu dem Paket gehört.
-
Die
Erfindung macht sich die Funktion der Version 1.2 des Java Development
Kit (JDK 1.2) zunutze, das die Versiegelung von Paketen ermöglicht. Diese
Eigenschaft, die hauptsächlich
dazu dient, eine höhere
Sicherheit zu bieten, wird von der Erfindung genutzt, um einen Aufruf
als einen so genannten "versiegelten
Aufruf" zu kennzeichnen,
wenn er in einem versiegelten und signierten Java-Paket erscheint
und wenn festgestellt wird, dass gewährleistet ist, dass alle Ziele
zu diesem Paket gehören.
-
Die
Feststellung von versiegelten Aufrufen und ihrer möglichen
Ziele vereinfacht aggressive (paketinterne) Optimierungen zwischen
den Prozeduren. Insbesondere kann ein versiegelter Aufruf, wenn er
nur ein einziges mögliches
Ziel hat, sicher durch Inlining ersetzt werden. Solche Optimierungen
können
in einem Just-In-Time-(JIT-)Compiler, in einem standardmäßigen Compiler,
der Kompilierungen zur Kompilierzeit (vor der Laufzeit) durchführt, oder
in Form von einer Bytecode-Umwandlungsstufe
stattfinden.
-
Erfindungsgemäß werden
bestimmte Szenarien beschrieben, in denen eine statische Analyse
sicher angewendet werden kann, ohne sich auf Annahmen stützen oder
Einschränkungen
des gesamten Programms hinnehmen zu müssen und ohne das Risiko eingehen
zu müssen,
dass sie veraltet sein wird oder einen Mehraufwand zur Laufzeit
verursacht. Es besteht die Möglichkeit,
das erfindungsgemäße Verfahren
in den Fällen
zu nutzen, in denen es anwendbar ist, und in anderen Fällen beliebige
der früheren Vorgehensweisen
zu befolgen.
-
Eine
bevorzugte Ausführungsform
wird nun lediglich anhand eines Beispiels, das nicht als Einschränkung zu
verstehen ist, und mit Bezug auf die beigefügten Zeichnungen beschrieben,
bei denen:
-
die 1a und 1b Flussdiagramme sind,
die die grundlegenden Arbeitsschritte in einem erfindungsgemäßen Verfahren
zeigen;
-
die 2, 3 und 4 Vererbungsszenarien
zeigen, die von der Erfindung verarbeitet werden, um Zugriffsbeschränkungen
festzulegen, die jeder Methode in dem Paket auferlegt werden;
-
die 5a, 5b, 6a, 6b und 7 Einzelheiten
des Codes zur Ausführung
von verschiedenen Software-Algorithmen in Verbindung mit der Erfindung
zeigen; und
-
8 ein
Blockschaubild ist, das funktionsgemäß ein System zur Feststellung
von "versiegelten Aufrufen" gemäß der Erfindung
zeigt.
-
Einführungshalber
sei angemerkt, dass in die Version 1.2 des Java Development Kit
(JDK 1.2) eine neue Funktion eingeführt wurde, die es ermöglicht,
Java-Pakete, die sich in JAR-Dateien befinden, zu "versiegeln". Wenn ein Paket
versiegelt ist, müssen
alle in dem Paket definierten Klassen und Schnittstellen von derselben
JAR-Datei stammen, andernfalls wird eine Ausnahme ausgeworfen ("java.lang.SecurityException"). Ein versiegeltes
Java-Paket wird in der gesamten Beschreibung und in den Ansprüchen als
ein "versiegeltes
Paket" bezeichnet.
Es ist auch möglich,
eine JAR-Datei zu versiegeln, was bedeutet, dass jedes Paket in
der JAR-Datei versiegelt ist, außer wenn es ausdrücklich als nicht
versiegelt angegeben ist. Wenn ein Paket in einer JAR-Datei versiegelt
ist, lädt
jede Anwendung alle betreffenden Klassen und Schnittstellen aus
dieser JAR-Datei (oder aber keine von ihnen). Der ursprüngliche
Beweggrund für
das Versiegeln von Paketen bestand in der Aufrechterhaltung der
Sicherheit. Wie jedoch zu sehen sein wird, macht sich die Erfindung
diese Eigenschaft von Java zunutze, um das freie Laden von Klassen
einzuschränken.
-
Die
Zusammenfassung von mehreren Klassendateien zu Gruppen ist für die Durchführung einer Analyse
zwischen Klassen wichtig. Um die Nachhaltigkeit einer solchen Analyse
sicherzustellen, muss es möglich
sein, Veränderungen
wie zum Beispiel die Modifikation und das Entfernen von vorhandenen Klassen
und die Einführung
von zusätzlichen
Klassen sowohl im Paket selbst als auch in der Versiegelung festzustellen.
Diesem Anspruch wird in der Form Rechnung getragen, dass JAR-Dateien
signiert werden (diese Funktion ist ebenfalls in JDK 1.2 verfügbar). Wenn
man die Klassen eines Java-Programms hat, ist es möglich, zu
prüfen,
ob sich alle derzeit möglichen
Ziele eines Aufrufs in einem versiegelten Paket befinden. Wenn nicht,
ist es möglich,
dass sich ein Teil der Ziele bis zur Laufzeit verändert. Aber selbst
wenn alle aktuellen Ziele zu einem einzigen versiegelten Paket gehören, besteht
immer noch die Möglichkeit,
dass weitere Ziele von anderen Paketen vor der Laufzeit oder zur
Laufzeit in Erscheinung treten. Folglich muss sichergestellt werden,
dass es für einen
Aufruf nicht möglich
ist, Methoden von einem anderen Paket als Ziel zu wählen; das
heißt,
es muss sichergestellt werden, dass der Aufruf ein versiegelter
Aufruf ist, so dass keine Änderungen
an aktuellen Zielen und keine zusätzlichen Ziele zur Laufzeit
erscheinen. Die Zugriffsbeschränkungen,
die von dem standardmäßigen (Paket-)Modifizierer
von Klassen, Schnittstellen und Methoden auferlegt werden, können genutzt
werden, um sicherzustellen, dass die Gruppe der Ziele nicht von
paketexternen Klassen vergrößert wird.
-
Auf
eine Klasse, eine Schnittstelle oder eine Methode, die über den
standardmäßigen (gepackten)
Modifizierer verfügt,
können
nur Klassen, Schnittstellen und Methoden innerhalb desselben Pakets
zugreifen. Eine Klasse, eine Schnittstelle oder eine Methode, die über den
standardmäßigen (gepackten)
Modifizierer verfüget,
wird als "gepackte" Klasse, "gepackte" Schnittstelle beziehungsweise "gepackte" Methode bezeichnet.
Eine gepackte Methode kann von einer Methode eines anderen Pakets nicht
direkt überschrieben
werden; nur auf öffentliche oder
geschützte
Methoden kann zugegriffen werden, und nur sie können von außerhalb des Pakets direkt überschrieben
werden. Eine gepackte Methode kann jedoch (direkt oder indirekt)
von einer öffentlichen oder
geschützten
Methode innerhalb desselben Pakets überschrieben und dann von einer
Methode eines beliebigen anderen Pakets (indirekt) überschrieben
werden. Um zu dem Schluss zu gelangen, dass keine externen Ziele
vorhanden sein können,
muss daher sichergestellt werden, dass eine gepackte Methode nicht
von einer öffentlichen
oder einer geschützten
Methode von innerhalb des Pakets überschrieben wird.
-
Eine
Methode, die zu einer gepackten (d.h. nichtöffentlichen) Klasse oder Schnittstelle
gehört, kann überdies
nicht direkt von einer Methode eines anderen Pakets überschrieben
werden; nur auf öffentliche
Klassen und Schnittstellen kann von außerhalb des Pakets zugegriffen
werden (und ihre Methoden können überschrieben
werden). Auch hier ist es möglich,
eine Methode einer gepackten Klasse mit Hilfe einer öffentlichen
Klasse zu "schmuggeln", welche die gepackte
Klasse von innerhalb des Pakets erweitert. Eine solche öffentliche
(Unter-)Klasse kann von einer anderen Klasse von außerhalb
des Pakets, die die ursprüngliche
Methode überschreiben
kann, erweitert werden. Eine Methode einer gepackten Schnittstelle
kann ebenfalls mit Hilfe einer Klasse, welche die Schnittstelle
implementiert, aus ihrem Paket heraus "geschmuggelt" werden, erbt aber eine Implementierung
für diese
Methode von einer Oberklasse, die sich außerhalb des Pakets befindet.
Ein weiterer Modifizierer spielt bei der Beschränkung der Möglichkeit, Java-Methoden zu überschreiben,
eine Rolle – bei
dem letzten Modifizierer ist das Überschreiben einer Methode
oder die Erweiterung einer Klasse streng verboten.
-
Die 1a und 1b sind
Flussdiagramme, die die wesentlichen erfindungsgemäßen Arbeitsschritte
zusammenfassen. Folglich wird jedes Paket, das analysiert werden
soll, geprüft,
um festzustellen, ob es sowohl versiegelt als auch signiert ist. Wenn
dies der Fall ist, wie in 1b gezeigt
ist, wird der Klassenhierarchiegraph (Class Hierarchy Graph (CHG))
des Pakets erstellt, und die jeweiligen Zugriffsberechtigungen der
Bestandteile des Pakets werden festgelegt. Diese werden zusammen
mit der Tatsache, dass das Paket bekanntermaßen versiegelt und signiert
ist, verwendet, um festzustellen, ob gewährleistet ist, dass alle Ziele
von Aufrufen in dem Paket zu dem Paket gehören. Nachdem somit festgestellt
wurde, dass der Aufruf "versiegelt" ist, kann dieses
Wissen beispielsweise umgehend genutzt werden, um eine Optimierung
des Compilers zu gestatten. Alternativ dazu kann dieses Wissen außerhalb des
Pakets zur späteren
unabhängigen
Verwendung gesondert gespeichert werden.
-
Wenn
festgestellt wird, dass das Paket nicht versiegelt und nicht signiert
ist, wird eine Prüfung durchgeführt, um festzustellen,
ob die Antworten auf die Frage, ob Aufrufe "versiegelt" sind, und mögliche Ziele der versiegelten
Aufrufe in das Paket aufgenommen werden sollen. Wenn nicht, wird
das Paket versiegelt und signiert, und die in 1b gezeigte Methode
wird ausgeführt,
wie vorstehend beschrieben wurde. Wenn die Antworten andererseits
in das Paket aufgenommen werden sollen, wird die in 1b gezeigte
Methode ausgeführt,
wie vorstehend beschrieben wurde, und die Antworten auf die Frage,
ob Aufrufe "versiegelt" sind, und mögliche Ziele
der versiegelten Aufrufe werden in dem Paket gespeichert. Daraufhin
wird das Paket versiegelt und signiert.
-
Es
sei angemerkt, dass der CHG von demselben Algorithmus, der auch
Zugriffsberechtigungen überprüft, als
ein zwangsläufig
durchgeführter
Schritt abgeleitet werden kann oder der CHG alternativ dazu extern
erstellt und in den Algorithmus eingegeben werden kann. Ebenso können auf
Wunsch auch die Zugriffsberechtigungen extern abgeleitet werden. Der
Algorithmus benötigt
diese Daten, doch macht es keinen Unterschied, ob sie extern oder
als Teil des Algorithmus selbst abgeleitet werden.
-
2 stellt
einen Vererbungsgraphen eines versiegelten Pakets bildlich dar,
der allgemein mit der Bezugszahl 10 angegeben ist und über die
Wurzelschnittstellen I1, I2 und I4 sowie eine Wurzelklasse CO verfügt. Ein
Aufruf in dem versiegelten Paket nimmt Bezug auf eine Methode m2,
die in der Klasse C2 gekapselt ist. Es wird festgestellt werden,
dass die Methode m2 von den Klassen C6 und C8 überschrieben wird, die beide
direkte Nachkommen der Klasse C2 in dem versiegelten Paket 10 sind.
von der Klasse C2 wird angenommen, dass sie selbst gepackt ist und
die Methode m2 als gepackte Methode deklariert. Folglich kann keine
Klasse außerhalb
des Pakets 10 die Klasse C2 erweitern oder die Methode m2
der Klasse C2 direkt überschreiben.
Von der Klasse C6 wird angenommen, dass sie die Methode m2 als öffentliche
und nichtfinale Methode deklariert, während die Klasse C8 die Methode
m2 als gepackte Methode deklariert, und von beiden Klassen wird
angenommen, dass es öffentliche
und nichtfinale Klassen sind. Folglich kann die Klasse C8 die Methode m2
nicht an die Klasse C9 weitergeben, die sich außerhalb des versiegelten Pakets 10 befindet,
da sie die Methode m2 als gepackte Methode deklariert. Jedoch können Nachkommen
der Klasse C6 selbst außerhalb
des versiegelten Pakets 10, wie zum Beispiel die Klasse
C7, die Methode m2 der Klasse C2 überschreiben, da die Klasse
C6 öffentlich
ist und die Methode m2 als öffentliche
und nichtfinale Methode deklariert hat. Dies bedeutet, dass nicht
gewährleistet werden
kann, dass Ziele eines Aufrufs der Methode m2 von der Klasse C2
zu dem versiegelten Paket 10 gehören, da sie der Klasse C7 außerhalb
des versiegelten Pakets zugeordnet werden könnten.
-
Unter
der Voraussetzung, dass festgestellt wird, dass das Paket 10 versiegelt
und signiert ist, kann folglich durch eine Analyse des CHG und der Zugriffsberechtigungen
der Klassen und Schnittstellen in dem Paket festgestellt werden,
ob gewährleistet
werden kann, dass alle Ziele eines Aufrufs zu dem versiegelten Paket
gehören.
Wenn ja, wird der Aufruf "versiegelt", und das Paket kann
auf sichere Weise optimiert werden; wenn nicht, können Methoden
außerhalb
des Pakets zur Laufzeit in dem Paket gekapselte Methoden überschreiben.
-
3 stellt
einen Vererbungsbaum eines versiegelten Pakets bildlich dar, der
allgemein mit der Bezugszahl 20 angegeben ist und über eine
Wurzelschnittstelle I2 verfügt,
die eine Methode m2 deklariert. Während zu sehen ist, dass die
Methode m2 in der Klasse C2 implementiert wird, ist diese Klasse kein
Nachkomme von I2. Jedoch wird I2::m2 von der Klasse C3 implementiert,
die wiederum m2 von der Klasse C2 außerhalb des Vererbungsbaums
von I2 erbt. Folglich kann eine fremde Methode von C2 über C3 in
das Paket "geschmuggelt" werden. Auf diese Weise
kann der Schmuggelvorgang über
eine Oberklasse stattfinden, wobei es keine Kontrolle über mögliche Ziele
eines Aufrufs der Methode I2::m2 gibt, wenn sich C2::m2 außerhalb
des Pakets befindet.
-
Wenn
der Aufruf von I2::m2 zur Laufzeit in die Klassen C3, C4, C5 oder
C6 aufgelöst
werden kann, muss gleichfalls festgestellt werden, ob die einzelnen
Klassen C3, C4, C5 oder C6 jeweils eine abstrakte Klasse sind oder über ihre
jeweils eigene Methode m2 verfügen,
und in einem solchen Fall kann der Aufruf von I2::m2 als "versiegelt" deklariert werden.
Wenn die Klasse abstrakt ist, kann sie selbst zur Laufzeit kein
tatsächliches
Ziel darstellen. Wenn sie über
ihre eigene Methode m2 verfügt,
wird anstelle von C2::m2 von außerhalb
des Pakets natürlich
ihre Methode aufgerufen.
-
4 stellt
zwei Vererbungsbäume
eines versiegelten Pakets bildlich dar, die allgemein mit der Bezugszahl 30 angegeben
sind und über
die Wurzelklassen C1 und C4 verfügen,
wobei die Klasse C1 die Methode m2 als eine öffentliche und nichtfinale
Methode deklariert. Die Klasse C3 ist ein öffentlicher und nichtfinaler
Nachkomme von C1. In demselben Paket haben die Klassen C4 und C6
keine direkte Vererbungsbeziehung zur Klasse C3 im Paket 30,
aber sie können
nicht übergangen
werden, da sie die Methode m2 von C3 über eine Zwischenklasse C7 überschreiben
können,
die nicht zu demselben Paket 30 gehört. Folglich besteht die Möglichkeit,
dass eine in einer Klasse d deklarierte Methode m von einer anderen
Klasse e in demselben versiegelten Paket, die keine erkennbare Vererbungsbeziehung
zur Klasse d hat, überschrieben
wird, und in diesem Fall, wenn e::m von außerhalb des Pakets überschrieben
werden kann, kann ein Aufruf von d::m nicht als "versiegelt" deklariert werden.
-
Die 5a bis 7 zeigen
Einzelheiten eines grundlegenden Algorithmus, der Zugriffsberechtigungen
sowohl von Klassen als auch von Mitgliedern (members) und den letzten
Modifizierer berücksichtigt,
um versiegelte Aufrufe festzustellen. Einige der Merkmale des Algorithmus
sind auch in den 2, 3 und 4,
die vorstehend beschrieben wurden, bildlich dargestellt.
-
Es
sei angenommen, dass wir einen Aufruf der Methode m von der Klasse
oder Schnittstelle c (bezeichnet mit c::m) erhalten und wir feststellen möchten, ob
dieser Aufruf ein versiegelter Aufruf ist. Vorausgesetzt, wir verfügen über das
Paket, das c enthält,
können
wir seine Klassen und Schnittstellen ermitteln und die hierarchischen
Vererbungsbeziehungen in Form von einem Klassenhierarchiegraphen
(CHG) oder einem Vererbungsgraphen aufzeichnen. Auf der Grundlage
des CHG kann eine Standardsuche nach allen Überschreibungen der Methode
c::m erweitert werden, um festzustellen, ob der erhaltene Aufruf
von c::m ein versiegelter Aufruf ist.
-
Alle
Unterklassen von c, die die Methode m möglicherweise überschreiben
könnten
(oder eine solche überschreibende
Implementierung von einer Oberklasse erben), werden bei diesem Suchlauf durchlaufen.
Bei diesen Unterklassen handelt es sich um all die Klassen d, die
die Klasse c direkt oder indirekt erweitern (oder die Schnittstelle
c implementieren), mit der Ausnahme, dass in dem Fall, in dem eine
Klasse die Methode m als finale Methode deklariert, alle ihre Unterklassen
ausgenommen werden. Wenn eine solche Klasse d angetroffen wird,
bei der es sich um eine öffentliche,
nichtfinale Klasse handelt, die die Methode m als eine öffentliche
und nichtfinale Methode neu deklariert oder eine solche Deklaration
von einer Oberklasse erbt, können
wir den Suchlauf beenden – der
ursprüngliche
Aufruf von c::m ist kein versiegelter Aufruf. Dies ist deshalb so, weil
die Klasse d von einer Unterklasse e eines anderen Pakets erweitert
werden kann und e c::m überschreiben
kann. Andernfalls ist der Aufruf von c::m ein versiegelter Aufruf.
-
Die 5a bis 7 stellen
eine Ausführung
eines solchen Algorithmus dar, der feststellt, ob ein Aufruf von
c::m ein versiegelter Aufruf ist. Die 6a und 6b behandeln
die Fälle,
in denen c eine Klasse ist; 7 behandelt
die Fälle,
in denen c eine Schnittstelle ist. Nachstehend folgt eine Beschreibung
eines Codes, der sich auf die Konstante PublicNonFinalSubclassFound
bezieht.
-
Zur
Feststellung einer direkten Vererbung zwischen Klassen und Schnittstellen
des Pakets genügt
es, einen Blick in diese Klassen und Schnittstellen zu werfen. Um
jedoch eine indirekte Vererbung festzustellen, kann es gegebenenfalls
notwendig sein, andere Klassen zu prüfen. Eine Klasse eines Pakets
kann zum Beispiel eine Klasse eines anderen Pakets erweitern, welche
wiederum eine andere Klasse des ersten Pakets erweitern kann. Folglich
erweitert die erstere Klasse (indirekt) die letztere Klasse, und
beide gehören
zu demselben Paket, aber wenn man nur in das Paket schaut, sind
keine Vererbungsbeziehungen sichtbar. Diese Situation ist in 4 mit
dem Paket 30 gezeigt. Die Zwischenklasse, die zu einem
anderen Paket gehört
(und der Klasse C7 in 4 entspricht), muss auch geprüft werden,
um die Vererbungsbeziehung uneingeschränkt feststellen zu können. Dennoch
soll unser Algorithmus nach wie vor nur auf dem Inhalt von einem
einzigen Paket beruhen: wenn es versiegelt und signiert ist, ist
es im Gegensatz zu anderen Informationen keinen Änderungen unterworfen.
-
Eine
solche paketübergreifende
indirekte Vererbung kann auch hilfreich sein, um eine Methode aus
ihrem Paket zu "schmuggeln". Eine gepackte Methode
einer öffentlichen
Klasse kann von einer indirekten Unterklasse in demselben Paket
(durch eine paketübergreifende
Vererbung) überschrieben
werden, und die Unterklasse kann die Methode wiederum als eine öffentliche
Methode deklarieren und dadurch externen Unterklassen ermöglichen,
sie zu überschreiben.
Dies ist jedoch der einzige Fall, der von Bedeutung ist: Die Klasse
muss öffentlich
sein, damit sie von einer Klasse eines anderen Pakets erweitert
werden kann, und die Methode muss gepackt sein, da sie andernfalls
direkt geschmuggelt werden kann. Methoden von Schnittstellen können nicht
auf diese Weise "geschmuggelt" werden, da sie alle öffentlich
sind.
-
Der
CHG des Pakets (der nur auf dem Inhalt des Pakets beruht) enthält keine
Bögen,
die auf eine paketübergreifende
Vererbung zurückzuführen sind. Eine
Möglichkeit,
vorsichtig mit dieser Unzulänglichkeit
umzugehen, besteht in der Annahme, dass zwei beliebige Klassen,
die in dem Vererbungsbaum des Pakets nicht miteinander verbunden
sind, einander indirekt erweitern können.
-
Die
in den 5a, 5b, 6a, 6b und 7 gezeigten
grundlegenden Algorithmen enthalten eine geringfügige Änderung, um der paketübergreifenden
Vererbung gerecht zu werden. Wenn eine öffentliche Unterklasse angetroffen
wird (zusammen mit einer gepackten nichtfinalen Deklaration der
Methode), die eine paketübergreifende
Erweiterung ermöglicht,
werden alle anderen Wurzelklassen des Pakets geprüft, um festzustellen,
ob sie die gepackte Methode möglicherweise überschreiben
können.
Es wird auf den Code verwiesen, der sich auf die Konstante PublicNonFinalSubclassFound
in den 5, 6a und 6b bezieht.
-
Betrachtet
man die Komplexität
des in den 5a, 5b, 6a, 6b und 7 gezeigten
Algorithmus, sei zunächst
angemerkt, dass sich der Vererbungsgraph eines Pakets wirksam erstellen lässt, wenn
man die Java-Klassendateien (Bytecodes) oder die Java-Quellendateien des
Pakets hat. Tatsächlich
kann jede Klasse nur einmal aufgerufen werden, um ihre Oberklasse,
ihre Oberschnittstellen und ihre deklarierten Methoden zu prüfen. Wenn
man den Vererbungsgraphen des Pakets hat, braucht der Algorithmus
jede Unterklasse und gelegentlich Oberklassen höchstens einmal aufzurufen.
Da eine Klasse (Schnittstelle) jedoch mehrere Schnittstellen implementieren
(erweitern) kann, darf der Vererbungsgraph kein Baum, sondern muss
ein allgemeiner gerichteter azyklischer Graph (directed acyclic
graph (DAG)) sein. Um zu verhindern, dass Klassen oder Schnittstellen
mehr als einmal aufgerufen werden, können Klassen und Schnittstellen
bei ihrem erstmaligen Aufruf daher gekennzeichnet werden. Ebenso kann
es eine Kennzeichnung für
(Ober-)Klassen, die "in
Aufwärtsrichtung" nach einer geerbten
Deklaration durchsucht wurden, und für "Wurzeln", die nach einer paketübergreifenden
Vererbung durchsucht wurden, geben, um zu vermeiden, dass solche
Suchläufe
wiederholt werden. Diese Änderungen
sind auch für
standardmäßige Algorithmen
von Bedeutung, die alle Überschreibungen
einer Methode finden.
-
Um
den Suchlauf nach einer möglichen
paketübergreifenden
Vererbung zu beschleunigen, kann der Vererbungsgraph vorverarbeitet
werden, so dass jede Wurzelklasse die Liste der Methoden enthält, die
in ihrem Vererbungsbaum (als öffentliche Methoden)
deklariert wurden (oder umgekehrt: Jede Methode kann den Klassen
und den Klassenbäumen zugeordnet
werden, in denen sie deklariert ist). Nur Methoden, die in zwei
verschiedenen Bäumen
erscheinen, sind von Interesse, und Bäume, deren Wurzeln java.lang.Object
direkt erweitern, können ausgelassen
werden, da "Object" keine andere Klasse
erweitert.
-
Der
Algorithmus zur Feststellung von versiegelten Aufrufen lässt sich
ohne weiteres so anpassen, dass die überschreibenden Implementierungen, die
während
des Suchlaufs gefunden werden, erfasst werden, damit sich die vollständige Gruppe
der Ziele für
versiegelte Aufrufe ermitteln lässt.
-
Bei
der Bewertung des erfindungsgemäßen Algorithmus
wurde festgestellt, dass ein sehr hoher Prozentsatz der Aufrufe,
die als versiegelt festgestellt wurden, genau ein mögliches
Ziel hatte und dass diese Aufrufe folglich gute Kandidaten für ein direktes
Inlining sind.
-
Die
Erfindung verwendet somit die standardmäßige Zugriffsberechtigung von
gepackten Klassen, Schnittstellen und Methoden, zusammen mit der Möglichkeit,
Java-Pakete zu versiegeln und zu signieren, um die vollständige Gruppe
der Ziele für
bestimmte Aufrufe zu ermitteln.
-
Eine
Möglichkeit,
diese Analyse zu verbessern und hoffentlich zusätzliche Aufrufe als versiegelte
Aufrufe zu kategorisieren, besteht in der Verwendung von Informationen über den
Lebendigkeitsanteil, wie von D. Bacon und P. Sweeney in "Fast Static Analysis
of C++ Virtual Function Calls" in
Proc. of the 1996 ACM Conference on Object Oriented Programming
Systems, Languages and Applications (OOPSLA), Seiten 325 bis 341,
San Jose, Kalifornien, Oktober 1996, beschrieben ist. Gepackte Klassen
oder Klassen ohne öffentliche
(oder geschützte)
Konstruktoren können
zum Beispiel nur als lebendig angesehen werden, wenn sie in dem
Paket instanziiert werden. Es gibt jedoch wenig Hoffnung, dass zusätzliche Aufrufe
auf diese Weise "versiegelt" werden können, da
eine öffentliche
Klasse (die bewirkt, dass der Aufruf nicht versiegelt wird) als
lebendig angesehen werden muss (wenn sie einen öffentlichen oder geschützten Konstruktor
hat). Andererseits besteht die Möglichkeit,
dass Informationen über
den Lebendigkeitsanteil die Anzahl der Ziele verringern können, die
ein versiegelter Aufruf bekanntermaßen hat.
-
Eine
andere Art und Weise, in der zusätzliche
Aufrufe versiegelt werden können,
ist die Durchführung
einer Datenflussanalyse, um die möglichen Arten der Klasse des
Aufgerufenen besser ermitteln zu können. Bei solchen Methoden
besteht die Möglichkeit,
zusätzliche
versiegelte Aufrufe festzustellen, sie sind jedoch weitaus komplexer
als der erfindungsgemäße Algorithmus
von dem Typ mit dem Vererbungsgraphen.
-
Man
wird bemerken, dass der erfindungsgemäße Algorithmus Aufrufe auf
der Grundlage von Informationen, die sich nur auf die aufgerufene
Methode beziehen, als versiegelte Aufrufe kategorisiert. Tatsächlich erkennt
der Algorithmus versiegelte Methoden – Methoden, die nur von innerhalb
desselben Pakets aufgerufen werden können –, und es ist gewährleistet,
dass ein jeder solcher Aufruf ein versiegelter Aufruf ist.
-
Entwickler
von Software können
die Leistungsfähigkeit
ihrer Anwendungen steigern, indem sie die standardmäßige (gepackte)
Zugriffsberechtigung für
entsprechende Klassen, Schnittstellen und Methoden verwenden und
ihre betreffenden Pakete versiegeln und signieren. Wenn der Schwerpunkt
der Arbeit einer Anwendung im Innern von Paketen und nicht in Aufrufen
zwischen Paketen liegt, kann sie auf sichere Weise beschleunigt
werden.
-
8 ist
ein Blockschaubild, das funktional ein Rechnersystem 40 zur
Feststellung von Aufrufen in einem versiegelten Java-Paket zeigt,
wobei sichergestellt ist, dass deren Ziele zu dem Paket gehören. Das
System 40 umfasst eine Bestätigungseinheit 41, um
zu bestätigen,
dass das Paket versiegelt und signiert ist, und eine CHG-Einheit 42,
um den Klassenhierarchiegraphen des Pakets festzustellen. Eine Zugriffsberechtigungseinheit 43 ist
bereitgestellt, um die Zugriffsberechtigungen der jeweiligen Komponenten in
dem Paket festzustellen. Ein Zielprozessor 44 ist mit der
Bestätigungseinheit 41,
der CHG-Einheit 42 und der Zugriffsberechtigungseinheit 43 verbunden und
reagiert auf den Klassenhierarchiegraphen und die Zugriffsberechtigungen
in Verbindung mit dem Wissen, dass das Paket versiegelt und signiert
ist, um festzustellen, ob gewährleistet
ist, dass alle Ziele der Aufrufe zu demselben Paket gehören. Der Zielprozessor 44 enthält eine
Einheit 45 zum direkten Überschreiben, um festzustellen,
ob eine aufgerufene Methode direkt überschrieben oder von einer
Methode von außerhalb
des Pakets implementiert werden kann, und wenn ja, um den Aufruf
als einen Aufruf zu kennzeichnen, bei dem nicht gewährleistet
ist, dass all seine Ziele zu demselben versiegelten Paket gehören.
-
Der
Zielprozessor 44 enthält überdies
eine Einheit 46 zum indirekten Überschreiben, um festzustellen,
ob eine aufgerufene Methode überschrieben oder
direkt oder indirekt von einer Methode in dem Paket implementiert
und dadurch von einer Methode von außerhalb des Pakets indirekt überschrieben oder
implementiert werden kann. Wenn ja, kennzeichnet das System 40 den
Aufruf als einen Aufruf, bei dem nicht gewährleistet ist, dass all seine
Ziele zu demselben versiegelten Paket gehören.
-
Die
Einheit 46 zum indirekten Überschreiben enthält eine
Unterklassen-Prüfeinheit 47,
um den Vererbungsgraphen zu durchlaufen, wobei bei der Klasse oder
Schnittstelle, die gerade verarbeitet wird, begonnen und mit jeder
Klasse oder Schnittstelle und zugehörigen Unterklassen und Unterschnittstellen
in dem Vererbungsgraphen fortgefahren wird. Die Unterklassen-Prüfeinheit 47 stellt
fest, ob es eine öffentliche
nichtfinale Unterklasse gibt, die die Methode als eine nichtfinale öffentliche
oder als eine geschützte Methode
deklariert oder eine solche Deklaration von einem Elternteil der
Klasse oder der Schnittstelle erbt. Wenn ja, kennzeichnet das System 40 den
Aufruf als einen Aufruf, bei dem nicht gewährleistet ist, dass all seine
Ziele zu demselben versiegelten Paket gehören.
-
Die
Einheit 46 zum indirekten Überschreiben enthält des Weiteren
eine Analyseeinheit 48 für mehrere Bäume, um festzustellen, ob die
Klasse von Klassen eines anderen Vererbungsbaums über eine oder
mehrere Klassen von außerhalb
des Pakets in Unterklassen unterteilt werden und die Methode an diese
Klassen weitergeben kann. Wenn ja, wird die Unterklassen-Prüfeinheit 47 hinsichtlich
eines jeden solchen Vererbungsbaums verwendet.
-
Die
Einheit 46 zum indirekten Überschreiben enthält darüber hinaus
eine Oberklassen-Prüfeinheit 49,
um zu prüfen,
ob die Schnittstelle von einer Methode einer "externen" Klasse von außerhalb des Pakets über eine "interne" Klasse innerhalb
des Pakets, welches die Schnittstelle implementiert und die Implementierung
der Methode von der externen Klasse erbt, implementiert werden kann,
so dass die interne Klasse instanziiert werden kann. Wenn ja, kennzeichnet
das System 40 den Aufruf als einen Aufruf, bei dem nicht
gewährleistet
ist, dass all seine Ziele zu demselben versiegelten Paket gehören.
-
Der
Zielprozessor enthält
ferner eine Einheit 50 für mögliche Ziele, um eine Gruppe
möglicher
Ziele für
einen Aufruf festzustellen, bei dem gewährleistet ist, dass all seine
Ziele zu demselben versiegelten Paket gehören.
-
Eine
Speichereinheit 51 ist mit dem Ausgang des Zielprozessors 44 verbunden,
um die Antworten auf die Frage zu speichern, ob Aufrufe in dem Paket als
Aufrufe gekennzeichnet sind, bei denen gewährleistet ist, dass all ihre
Ziele zu demselben versiegelten Paket gehören. Wie vorstehend mit Bezug
auf 1a angemerkt wurde, kann diese Information getrennt
innerhalb oder außerhalb
des Pakets zur unabhängigen
späteren
Verwendung gespeichert werden.
-
Eine
Programm-Analyseeinheit 52 ist auch mit dem Ausgang des
Zielprozessors 44 verbunden, um eine Programmanalyse zwischen
Prozeduren eines Java-Pakets unter Verwendung von Informationen über Aufrufe
durchzuführen,
bei denen gewährleistet
ist, dass ihre Ziele zu demselben Paket gehören. Ebenfalls mit dem Ausgang
des Zielprozessors 44 verbunden ist eine Optimierungseinheit 53,
die eine Code- und/oder Compileroptimierung eines Java-Pakets zwischen
Prozeduren unter Verwendung von Informationen über Aufrufe durchführt, bei
denen gewährleistet
ist, dass ihre Ziele zu demselben Paket gehören. Mit der Optimierungseinheit 53 ist
ein Speichermedium 54 verbunden, auf dem ein optimiertes kompiliertes
Programm gespeichert werden kann, das von der Optimierungseinheit 53 abgeleitet
wird.
-
Es
wird als vorteilhaft erkannt werden, dass die Erfindung auch ein
Speichermedium vorsieht, das ein Rechnerprogramm zur Durchführung der
Erfindung speichert, sowie ein Speichermedium, das kompilierte Programmdaten
speichert, die erfindungsgemäß erzeugt
wurden.
-
In
den Verfahrensansprüchen,
die folgen, werden die Buchstaben, die zur Bezeichnung der Schritte
der Ansprüche
verwendet werden, lediglich aus Gründen der Einfachheit verwendet,
und sie bedeuten keine bestimmte Reihenfolge, in der die Schritte
durchgeführt
werden sollen. Ebenso wird als vorteilhaft erkannt werden, dass
die Reihenfolge, in der Aufrufe als "versiegelte" Aufrufe ausgeschlossen werden, unwichtig
ist, und folglich sollte die Reihenfolge, in der die Verfahrensansprüche erscheinen, nicht
als bindend verstanden werden.