The Art of Runtime Development

Android Runtimes im Vergleich: ART vs. Dalvik VM

Lars Röwekamp, Arne Limburg

© Shutterstock.com / Leszek Glasner

Bereits zur Google IO im Juni 2014 stellte Google eine neue Runtime vor: die ART (kurz für Android Runtime). Diese wird bereits mit Android KitKat ausgeliefert. Die Standard-Runtime ist dort allerdings weiterhin die Dalvik VM. Diese kann im Einstellungsmenü aber zugunsten von ART deaktiviert werden. Mit Android 5 (Lollipop) wird die Dalvik VM nun gar nicht mehr ausgeliefert und ist komplett durch ART ersetzt worden. Worin liegt aber der Unterschied von ART zu Dalvik und was bedeutet das für den Entwickler? Diese Kolumne erläutert die Änderungen und geht auf deren Auswirkungen ein.

Vor einigen Monaten berichteten wir bereits über das Projekt Volta, mit dem es Google gelungen ist, die Batterielaufzeit für Android-betriebene Devices deutlich zu erhöhen. Im Rahmen der damaligen Kolumne erwähnten wir auch die neue Virtual Machine ART, die in Android 5 Dalvik ersetzt. Sie ist zwar nicht Teil von Projekt Volta, ihr kann aber auch ein nicht unerheblicher Anteil an der Erhöhung der Batterielaufzeit zugerechnet werden, weil sie z. B. Garbage Collection deutlich performanter gestaltet. Dadurch wird nicht nur Zeit gewonnen, sondern auch Akku geschont.

Woran liegt das aber genau und welche Folgen hat die Änderung der Runtime für Entwickler? Diese Kolumne gibt einen kleinen Einblick in die Unterschiede zur bisherigen Dalvik VM.

Lars Röwekamp spricht live auf der MobileTech Conference 2015 in Berlin:

In diesen MobileTech Conference Sessions erfahren Sie mehr über Android & Co.:

.
.
.
.
.

Ahead-of-Time statt Just-in-Time

Android-Apps werden (größtenteils jedenfalls) in Java geschrieben. Java ist eine interpretierte Sprache, d. h. ein Java-Programm wird vom Compiler nicht direkt in Maschinensprache übersetzt, sondern in eine maschinenunabhängige Zwischensprache, den Bytecode. In Android wird das verwendete Bytecodeformat .dex (Dalvik Executable) genannt. Die entstehenden .dex-Dateien werden zusammen mit den Ressourcen und ggf. dem nativen Code in ein Android Application Package (APK) zusammengepackt. Der .dex-Code wird dann auf dem Gerät vor der Ausführung in den tatsächlichen Maschinencode übersetzt. Auf diese Weise ist es auch möglich, dass Android unterschiedliche Zielarchitekturen unterstützt.

Was ändert sich daran nun mit ART? Bis zur Installation bleibt alles beim Alten. Android-Programme werden weiterhin in das .dex-Format kompiliert und in APKs verpackt. Das hat zur Folge, dass auch alle alten Android-Programme (APKs) ohne Änderung auf ART laufen, solange sie keinen nativen Code enthalten, sondern zu 100 Prozent in Java geschrieben sind.

Die entscheidende Neuerung passiert erst auf dem Gerät. Bei Dalvik werden die .dex-Dateien nahezu unverändert auf dem Gerät abgelegt (um genau zu sein, werden sie mit einem Tool namens Odex vorher noch optimiert). Wenn das Programm gestartet wird, beginnt der Just-in-Time-Compiler Stück für Stück damit, den .dex-Code in Maschinensprache zu übersetzen, und zwar immer das Stück, das als Nächstes ausgeführt wird. Natürlich cacht er dabei bereits kompilierte Teile der Applikation während der Ausführung, sodass kein Teil zweimal kompiliert werden muss. Dieser Cache arbeitet aber nach dem Least-Recently-Used-Algorithums, d. h., seltener benutzte Programme werden aus dem Cache entfernt. Wenn sie erneut ausgeführt werden, müssen sie vorher auch erneut kompiliert werden. Die Just-in-Time-Kompilation hat den Nachteil, dass sie eben während der Programmausführung stattfindet und damit, weil sie selbst auch Prozessorzeit benötigt, die Ausführung verlangsamt.

ART geht daher einen anderen Weg. Es kompiliert die .dex-Dateien bereits bei der Installation einer App. Auf dem Gerät liegt dann also nicht nur der .dex-Bytecode, sondern immer auch der kompilierte Maschinencode vor. Dieser Maschinencode ist dann auch bereits für die ausführende Plattform optimiert, d. h., es gibt einen Compiler für die ARM-Architektur, einen für MIPS und einen für x86. Genauer gesagt gibt es sogar jeweils zwei, einen für 32 Bit und einen für 64 Bit. Dazu aber später mehr. Durch das Kompilieren bei der Installation entfällt die performanceintensive Just-in-Time-Kompilation zur Laufzeit der App (Abb. 1). Außerdem dauert das Kompilieren nur wenige Sekunden, sodass die längere Installationszeit nicht ins Gewicht fällt.

ART nimmt noch weitere Optimierungen an den installierten Apps vor. So wird z. B. der initiale Heap auch direkt mit abgelegt, der die geladenen Class-Dateien und statischen Variableninstanzen enthält. Auf diese Weise entfallen beim App-Start teure Class-Initialisierungschecks.

Abb. 1: Durch das Kompilieren bei der Installation entfällt die performanceintensive Just-in-Time-Kompilation zur Laufzeit der App (Nachbildung des in [1] gezeigten Schaubilds „The life of an APK“)

Abb. 1: Durch das Kompilieren bei der Installation entfällt die performanceintensive Just-in-Time-Kompilation zur Laufzeit der App (Nachbildung des im Video gezeigten Schaubilds „The life of an APK“)

Optimierte Garbage Collection

Einer der großen Unterschiede von Low-Level-Programmiersprachen wie C oder C++ zu Java ist, dass sich der Entwickler in Java nicht um Memory-Management kümmern muss. Möglich macht dies der Garbage Collector, der von Zeit zu Zeit im Heap „aufräumt“ und den Speicher von nicht mehr benötigten Objekten freigibt. Um zu verstehen, wie der Garbage Collector in ART optimiert wurde, ist es zunächst einmal wichtig zu verstehen, wie der Garbage Collector generell funktioniert.

Um herauszubekommen, welche Objekte nicht mehr benötigt werden, navigiert der Garbage Collector über die Stackframes aller Threads und markiert die Objekte im Heap, die direkt von einem Thread referenziert werden. All diese Objekte werden benötigt. In einem zweiten Schritt durchläuft er die markierten Objekte im Heap und markiert wiederum alle Objekte, die von den markierten Objekten benötigt werden. Danach kann er alle nicht markierten Objekte freigeben. Idealerweise laufen diese Arbeiten parallel zur Ausführung der Applikation, damit diese nicht unterbrochen wird, was zu unschönen Pausen führen würde. Es gibt aber Teile, die nicht parallel ausgeführt werden können, sodass der Garbage Collector von Zeit zu Zeit alle Threads stoppt, um seine Arbeit ausführen zu können. Der Garbage Collector der Dalvik VM tut dies an zwei Stellen während der Ausführung: das erste Mal, während er durch den Stack läuft und das zweite Mal, während er durch den Heap läuft. Beides ist nachvollziehbar. Wenn ein Thread ein neues Objekt anlegt, während der Garbage Collector gerade markiert, könnte es sein, dass dieses Objekt „aus Versehen“ nicht markiert wird und dadurch später entfernt wird. Das zweite Stoppen wurde bereits in Dalvik optimiert: Das eigentliche Durchlaufen des Heaps läuft bereits parallel. Danach erfolgt ein kurzes Stoppen der Threads, um zu überprüfen, ob beim parallelen Durchlaufen durch einen Thread eine Änderung vorgenommen wurde.

ART geht hier einen Schritt weiter. Das initiale Stoppen der Threads wurde abgeschafft. Möglich wird dies, weil das Markieren der Objekte nicht mehr durch den Garbage Collector vorgenommen wird, sondern durch die Applikation selbst. Auch die Länge der zweiten Pause wurde deutlich verkürzt.

Zu der Verbesserung der Laufzeit des Garbage Collectors kommen noch ein paar weitere Optimierungen im Bereich Garbage Collection und Memory Management hinzu. So wurde in Dalvik z. B. sehr viel Wert darauf gelegt, Fragmentierung des Speichers zu verhindern. Fragmentierung entsteht, wenn ein großes Objekt im Speicher freigegeben wird und der Platz danach durch ein kleines belegt wird. Dadurch findet das nächste große Objekt keinen Platz mehr, obwohl für das kleine eventuell noch an anderer Stelle Platz gewesen wäre. Um Fragmentierung zu verhindern, wurde in Dalvik regelmäßig vor der Allozierung der Garbage Collector ausgeführt, um die kleinstmögliche Stelle zu finden, in die das zu allozierende Objekt passt. Diese GC-Ausführungen (im Log unter GC_FOR_ALLOC zu finden) werden in ART komplett vermieden, was alleine schon zu einer deutlichen Performancesteigerung führt. Möglich wird das in ART dadurch, dass ein separater Bereich im Heap speziell für große Objekte bereitgestellt wird, die aus Arrays von primitiven Datentypen (Bytes, Integers, …) bestehen. Man hat festgestellt, dass die meisten großen Objekte aus solchen Arrays bestehen (die meisten großen Objekte sind Bilder). Durch das Auslagern solcher Objekte in einen separaten Bereich des Heaps verhindert man Fragmentierung. Solche großen Objekte haben einen weiteren Vorteil: In der Regel referenzieren sie selbst keine weiteren Objekte. Aufbauend darauf können weitere GC-Optimierungen beim Markieren der Objekte vorgenommen werden.

Eine weitere Optimierung ist die Einführung einer neuen Methode zur Speicherreservierung. Früher wurde wie in allen Unix-Systemen malloc verwendet. Nun wurde rosalloc implementiert und eingesetzt. Dieser ist speziell auf die Verwendung von Objektorientierung optimiert und kommt mit weniger Dateisperren aus, z. B. indem er kleine Objekte Thread-local reserviert.

64-Bit-Unterstützung

Eine weitere wichtige Neuerung von ART ist die Unterstützung von 64-Bit. Dies geschieht, wie bereits erwähnt, indem für die Compiler der verschiedenen Architekturen jeweils zwei Versionen existieren, eine 32-Bit-Version und eine 64-Bit-Version. Unterstützt die Zielplattform 64-Bit, so wird der 64-Bit-Compiler verwendet.

In der normalen Programmausführung wird allerdings durch 64-Bit in der Regel kein großer Performanceunterschied zu messen sein. Dennoch bietet 64-Bit einige Vorteile, die teilweise auch erst in der Zukunft zum Tragen kommen werden. Die größten Performanceunterschiede wird es bei der Berechnung mathematischer Operationen geben, die mit großer Genauigkeit arbeiten. Diese können von den 64-Bit-Prozessoren besser Gebrauch machen. Gleiches gilt für Multimediaanwendungen. Erst in Zukunft eine Rolle spielen wird die Möglichkeit, größere Speicherbereiche zu adressieren. Mit 32 Bit ist es nämlich nicht möglich, RAM zu adressieren, der größer als 4 GB ist. Da bisher die meisten Geräte ohnehin noch nicht viel RAM haben, spielt das aber noch keine Rolle. ART ist jetzt allerdings für die Zukunft gerüstet.

Verbesserte Fehlermeldungen und besseres Debugging

ART kommt mit ein paar weiteren kleineren Verbesserungen, die das Profiling und Debugging betreffen. Bisher konnte Traceview als Profiler verwendet werden. Dieser bringt aber selbst einen nicht unerheblichen Overhead bei Methodenaufrufen mit. Mit dem neuen Sampling Profiler wird dieser Overhead nahezu auf null reduziert. Des Weiteren stehen während des Debuggings deutlich mehr Informationen zur Verfügung, z. B. alle Instanzen einer Klasse. Auch besteht die Möglichkeit, Breakpoints pro Instanz zu filtern.

Eine weitere praktische Neuerung bietet auch das Exception Handling. Beim Auftreten von ClassNotFoundExceptions, ClassCastExceptions und NullPointerExceptions stehen nun deutlich mehr Informationen zur Verfügung. Das ist ja bereits seit neueren Dalvik-Versionen auch für ArrayIndexOutOfBoundsExceptions und ArrayStoreExceptions der Fall. Eine NullPointerException teilt nun z. B. mit, auf welchem Objekttyp sie aufgetreten ist und auf welches Feld zugegriffen werden sollte bzw. welche Methode aufgerufen werden sollte.

Fazit

Durch den Wechsel der Runtime – weg von Dalvik, hin zu ART – konnte Google in Android 5 (Lollipop) erneut eine so hohe Performancesteigerung erreichen, wie sie zuletzt wahrscheinlich nur durch Project Butter beim Wechsel von Android 4.0 auf Android 4.1 erzielt wurde.
Erreicht wurde dies durch Ahead-of-Time-Kompilation statt Just-in-Time-Kompilation, verbesserte Garbage Collection und 64-Bit-Unterstützung.

Die gute Nachricht dabei ist: Die meisten Entwickler müssen nichts tun, damit ihre Apps in den Genuss dieser Optimierungen kommen. Alle Apps, die komplett in Java geschrieben sind, laufen automatisch auf ART. Laut Google sind das 85 Prozent der Apps im Play Store.
Apps, die nativen Code verwenden (also das NDK), sollten hingegen getestet werden. Dazu hat Google eine kleine Checkliste erarbeitet.

Mit dieser Kolumne haben wir einen Eindruck der Unterschiede zwischen ART und Dalvik gegeben. Da sich die wenigsten von uns in der Luxussituation befinden, von nun an nur noch für Android 5 entwickeln zu müssen, wird uns die Dalvik VM allerdings noch einige Jahre erhalten bleiben. Von Android 5 an ist Dalvik allerdings Geschichte und wird damit mittelfristig von den Android-Devices verschwinden. In diesem Sinne: Stay tuned.

Aufmacherbild: Art word on vintage via Shutterstock.com / Urheberrecht: Leszek Glasner

Geschrieben von
Lars Röwekamp
Lars Röwekamp
Lars Röwekamp ist Gründer des IT-Beratungs- und Entwicklungsunternehmens open knowledge GmbH, beschäftigt sich im Rahmen seiner Tätigkeit als „CIO New Technologies“ mit der eingehenden Analyse und Bewertung neuer Software- und Technologietrends. Ein besonderer Schwerpunkt seiner Arbeit liegt derzeit in den Bereichen Enterprise und Mobile Computing, wobei neben Design- und Architekturfragen insbesondere die Real-Life-Aspekte im Fokus seiner Betrachtung stehen. Lars Röwekamp, Autor mehrerer Fachartikel und -bücher, beschäftigt sich seit der Geburtsstunde von Java mit dieser Programmiersprache, wobei er einen Großteil seiner praktischen Erfahrungen im Rahmen großer internationaler Projekte sammeln konnte.
Arne Limburg
Arne Limburg
Arne Limburg ist Softwarearchitekt bei der open knowledge GmbH in Oldenburg. Er verfügt über langjährige Erfahrung als Entwickler, Architekt und Consultant im Java-Umfeld und ist auch seit der ersten Stunde im Android-Umfeld aktiv.
Kommentare

Hinterlasse einen Kommentar

avatar
4000
  Subscribe  
Benachrichtige mich zu: