4. 4
Ein Garbage Collector sorgt dafür dass nur die Objekte dir noch erreichbar sind im Speicher bleiben.
Nicht erreichbare Objekte können dem Programm nicht mehr von Nutzen sein und können daher
gelöscht werden.
Wann der GC Objekte löscht ist typischerweise nicht determiniert. Dies steht im Gegensatz zu
Reference Counting, dass z.B. in C++ gerne als einfache automatische Speicherverwaltung genutzt
wird.
5. 5
Malloc muss normalerweise eine Liste freier Speicherbereiche verwalten ->Malloc typischerweise
langsamer als ein new in Java
6. 6
Das eigentliche Problem sind die versteckten Kosten des GC.
Praktisch jede GC Implementierung erfordert eine „stop the world“ Phase, bei der alle
Applikationsthreads angehalten werden müssen.
Der Benutzer hat dann den Eindruck einer nicht vorhersagbaren Antwortzeit.
7. 7
„Normaler“ Garbage Collector log
http://www.tagtraum.com/gcviewer.html
Der Speicherverbrauchstrend zeigt keinen Anstieg. Daher liegt hier wohl kein Memory Leak vor
Je kleiner der Speicherverbrauch nach einem Full GC ist desto weniger Full GC‘s müssen bei sonst
gleichen Bedinungen ausgeführt werden
9. 9
Shallow size == flache Grösse des Objektes. Die Shallow Size ist in der Praxis ein nützliches Maß für
den Overhead eines Objektes.
„object header“ : Jedes Java Object (in der SUN/SAP JVM) hat einen Header, der Informationen über
das Object Layout, Type, GC Zustand, Synchronization Status, und identity hash code enthält. Der
Object header ist 2 Worte (8Bytes unter 32 bit) groß.
10. 10
Man beachte :
Mehrere Stringobjekte können das gleiche char[] verwenden (sharing)
Selbst ein leerer String braucht mehr als 24 bytes wenn das leere char[] nicht gemeinsam verwendet
wird.
11. 11
Diese Werte beziehen sich auf eine SUN JVM für 32 bit Intel Prozessoren. Bei anderen JVM‘s oder
anderen Prozessoren kann der Speicherverbrauch anders aussehen.
12. 12
Unter 64 bit braucht eine Java Applikation signifikant mehr Speicher als unter 32 bit, weil Referenzen
8 Byte statt 4 Byte benötigen. Der Unterschied im Speicherverbrauch hängt daher vor allem davon ab
wieviele Referenzen es gibt.
Bei identischer Hardware (Speichergröße) bringt der Übergang auf 64 bit daher zunächst oft keinen
Performancegewinn. Allerdings ist unter 32 Bit Windows die maximale Größe des Java Heaps auf
nicht viel mehr als 1 Gbyte beschränkt (etwas mehr unter Linux und bis zu 3,8 Gbyte unter Solaris).
13. 13
In diesem vereinfachten Beispiel können folgende Schlüsse aus den Daten gezogen werden :
Es wird am meisten Speicher in char[] verbraucht
Die Anzahl der String Objekte ist etwas kleiner als die der char[]. Es wird aus den Daten nicht klar ob
die vielen String Objekte char[] „sharen
Es ist nicht klar ob möglicherweise com.erp.Order viele Stringobjekte referenziert und dadurch
letztlich den hohen Speicherverbrauch verursacht
14. 14
Da die shallow size in der Praxis nicht sehr nützlich ist, braucht man ein Maß für die Menge des
Speichers die festgehalten wird.
Dazu simuliert man einen GC Lauf unter der Annahme, das eine bestimme Menge von Objekten nicht
mehr existieren würde.
15. 15
Hier ist ein Beispiel der LinkedList Klasse aus dem JDK dargestellt
18. 18
Ein Heap Dump ist ein File, dass alle „noch lebendenden“ Java Objekte zu einem bestimmten
Zeitpunkt enthält.
„Noch lebenden“= Erreichbar
Genaugenommen kann ein Heap dump auch noch Objekte enthalten, die nicht mehr referenziert
werden. Im Allgemeinen werden diese Objekte von den gängigen Heap dump Analyse Tools
allerdings nicht betrachtet.
Vor einem Heap dump wird ein Full GC ausgelöst.
19. 19
Dies gilt zumindest für die SUN/SAP JVM.
Andere JVM‘S (IBM) schreiben Heap Dumps, die einige Informationen nicht enthalten
20. 20
Es gibt einige verschiedene Möglichkeiten einen Heap dump zu erzeugen
23. 23
Das IBM Heap Dump Format wird durch ein zusätzliches Plugin unterstützt
MAT kann sowohl standalone als auch innerhalb von Eclipse benutzt werden
26. 26
Objekte mit großer retained Size in einem großen Heap Dump mit Millionen von Objekten zu finden
ist manuell sehr schwierig.
Ein naiver Algorithmus um die größten Objekte zu finden,könnte für jedes Objekt im Heap eine
Simulation einer Garbage Collection durchführen. Dies würde allerdings O(n^2) Aufwand bedeuten.
Nimmt man z.B. 5 Sekunden für eine GC Simulation an und hat einen Heap mit 10 Millionen
Objekten so bräuchte man mit diesem Algorithmus schon mehr als 1,5 Jahre. Außerdem hätte man
damit noch nicht die Information über die immediate Dominatoren der Objekte.
Der durch die immediate Dominatoren aufgespannte „Dominator tree“ erlaubt es unter anderem die
retained Size aller Objekte vorzuberechnen und der Größe nach zu sortieren.
Der Dominator Tree kann in „fast“ O(n) (O(m*agr;(m, n); agr == Inversion der Ackermannfunktion) Zeit
mit dem Lengauer Tarjan Algorithmus (http://portal.acm.org/citation.cfm?id=357071&dl=GUIDE,)
berechnet werden.
27. 27
Das Business Objekt vom Typ com.myerp.buyer.Order referenziert einen LinkedList die String
Objekte enthält.
LinkedList$Entry2 ist der immediate Dominator von String 2
28. 28
Der Immediate Dominator von LinkedList$Entry2 ist LinkedList$Entry0.
LinkedList$Entry1 ist kein Dominator von LinkedList$Entry2, da beim Entfernen von
LinkedList$Entry 1 weiterhin eine Referenz von
LinkedList$Entry0 auf LinkedList$Entry 2 besteht.
30. 30
Das com.myerp.buyer.Order Objekt „domiert die 3 String Objekte. Das heißt, dass wenn man diese
Objekt aus dem Speicher entfernen würde dann würde der GC auch die 3 Strings freigeben. Daß sich
dazwischen eine Java Collection Klasse befindet ist für die Analyse meist nicht von Interesse. Es kann
daher nützlich sein im Dominator Tree Knoten herauszufiltern. Typischerweise möchte man Instanzen
von Java Standard library Klassen herausfiltern.
33. 33
Im Eclipse Debugger kann man dies leicht nachvollziehen. Der Grund für dieses Verhalten ist
die effiziente Implementierung von StringBuffer.toString(). Diese kopiert nämlich das interne
char[] des StringBuffer‘s nicht sondern verwendet es für den neu erzeugten String (unter JDK
1.4).
34. 34
Im Eclipse Debugger kann man dies leicht nachvollziehen. Der Grund für dieses Verhalten ist
die effiziente Implementierung von StringBuffer.toString(). Diese kopiert nämlich das interne
char[] des StringBuffer‘s nicht sondern verwendet es für den neu erzeugten String (unter JDK
1.4).
35. 35
Dieses Verhalten ist so gewollt. Der Vorteil ist dass mehrere String Objekte sich ein char[]
teilen können.
37. 37
XML DOM Bäume brauchen sehr viel Speicher, und sollten daher nicht direkt als
Datenstruktur verwendet werden.
Wenn der XML Parser alte Daten referenziert wird unnötig Speicher verbraucht.
XSLT Transformerobjekte können mehrere Mbyte verbrauchen. Da die Transformerobjekte
relativ teuer zu erzeugen sind kann man durch Pooling versuchen immer nur einen begrenzte
Zahl im Speicher zu halten.
38. 38
Caches sind relativ aufwendig zu implementieren, da nicht nur der Cache selber
implementiert werden muss sonder auch die Infrastruktur zum Monitoring und zur
Administration. Daher sollte man auf eine existierende stabile Cacheimplementierung
zurückgreifen.
Ist eine Webseite „stateless“, kann also der Inhalt der Seite nur aus den Parametern des
Request berechnet werden, so kann sie auch gut vom Browser gecached werden.
39. 39
Es gibt einige Strategien zur Minimierung des Speicherverbrauchs. Hier nur die wichtigsten.
Der Memory Analyzer bietet z.B. Queries für das finden leerer Collections an.