2009-03-09 1 views
36

Ich übernehme einige Anwendungen von einem früheren Entwickler. Wenn ich die Anwendungen über Eclipse ausführe, sehe ich, dass die Speichernutzung und die Heap-Größe stark zunehmen. Bei weiteren Untersuchungen sehe ich, dass sie ein Objekt sowohl in einer Schleife als auch in anderen Dingen über und über erstellen.Welche Best Practices für die Java-Speicherverwaltung gibt es?

Ich fing an, durch zu gehen und etwas aufräumen. Aber je mehr ich durchging, desto mehr Fragen, die ich hatte, "wird das tatsächlich irgendetwas tun?"

Anstatt beispielsweise eine Variable außerhalb der oben genannten Schleife zu deklarieren und nur ihren Wert in der Schleife zu setzen, haben sie das Objekt in der Schleife erstellt. Was ich meine ist:

for(int i=0; i < arrayOfStuff.size(); i++) { 
    String something = (String) arrayOfStuff.get(i); 
    ... 
} 

gegen

String something = null; 
for(int i=0; i < arrayOfStuff.size(); i++) { 
    something = (String) arrayOfStuff.get(i); 
} 

Bin ich falsch zu sagen, dass die untere Schleife ist besser? Vielleicht liege ich falsch.

Auch, was ist nach der zweiten Schleife oben, setze ich "etwas" zurück auf null? Würde das etwas Speicher löschen?

In beiden Fällen, was sind einige gute Speicherverwaltung Best Practices, die ich folgen konnte, die dazu beitragen, meine Speicherauslastung in meinen Anwendungen niedrig zu halten?

Update:

ich jedermanns Feedback schätzen so weit. Allerdings habe ich nicht wirklich nach den obigen Loops gefragt (obwohl ich durch deinen Ratschlag zurück zur ersten Runde gegangen bin). Ich versuche, einige Best Practices zu bekommen, nach denen ich Ausschau halten kann. Etwas im Sinne von "Wenn Sie mit einer Sammlung fertig sind, löschen Sie sie". Ich muss nur wirklich sicherstellen, dass nicht so viel Speicher von diesen Anwendungen belegt wird.

+0

Re Ihre Bearbeitung: Wie drei Leute bisher gesagt haben, Profil es! –

+0

Ich bin auf der Suche nach bestimmten Praktiken zu verwenden, damit ich nicht jemanden Profil haben muss. Ich würde es lieber von vornherein richtig entwickeln als programmieren, hoffen, dass es funktioniert, es profilieren und Korrekturen vornehmen. – Ascalonian

+2

Die "spezifische Vorgehensweise" besteht darin, einen gut strukturierten Code ohne vorzeitige Optimierung zu schreiben und ihn dann zu profilieren, um herauszufinden, was optimiert werden muss. –

Antwort

36

Versuchen Sie nicht, die VM zu überlisten. Die erste Schleife ist die empfohlene Best Practice, sowohl für die Leistung als auch für die Wartbarkeit. Wenn die Referenz nach der Schleife auf null zurückgesetzt wird, ist keine sofortige Freigabe des Speichers gewährleistet. Der GC wird seine Aufgabe am besten erfüllen, wenn Sie den geringst möglichen Umfang verwenden.

Bücher, die diese Dinge im Detail (aus der Sicht des Benutzers) abdecken, sind Effective Java 2 und Implementation Patterns.

Wenn Sie mehr über die Leistung und die Vorteile der VM erfahren möchten, müssen Sie Vorträge sehen oder Bücher von Brian Goetz lesen.

+0

" verwenden minimalen Umfang möglich "ist es eine Best Practice im Allgemeinen? – KillBill

+0

Ich denke das generell ja. Aber natürlich sollte es Ausnahmen von dieser Verallgemeinerung geben. – cherouvim

9

Diese beiden Schleifen sind äquivalent mit Ausnahme des Bereichs something; Details siehe this question.

Allgemeine Best Practices? Ähm, mal sehen: Speichern Sie keine großen Datenmengen in statischen Variablen, es sei denn, Sie haben einen guten Grund. Entfernen Sie große Objekte aus Sammlungen, wenn Sie fertig sind. Und oh ja, "Messen Sie, raten Sie nicht." Verwenden Sie einen Profiler, um zu sehen, wo der Speicher zugewiesen wird.

+3

+1 nur für "Messen, nicht raten." –

5

Die beiden Schleifen verwenden grundsätzlich die gleiche Menge an Speicher, jeder Unterschied wäre vernachlässigbar. "String something" erstellt nur einen Verweis auf ein Objekt, nicht ein neues Objekt in sich selbst, und daher ist zusätzlicher Speicher, der verwendet wird, klein. Plus, Compiler/kombiniert mit JVM wird wahrscheinlich den generierten Code sowieso optimieren.

Für Speicherverwaltungspraktiken sollten Sie wirklich versuchen, Ihren Speicher besser zu profilieren, um zu verstehen, wo die Engpässe tatsächlich sind.Achten Sie besonders auf statische Referenzen, die auf einen großen Speicherbereich verweisen, da dieser niemals gesammelt wird.

Sie können auch schwache Referenzen und andere spezielle Speicherverwaltungsklassen anzeigen.

Schließlich, denken Sie daran, dass, wenn ein Anwendungsspeicher in Anspruch nimmt, könnte ein Grund dafür sein ....

aktualisiert Der Schlüssel zur Speicherverwaltung Datenstrukturen, sowie wie viel Leistung, die Sie brauchen/wann. Der Kompromiss besteht häufig zwischen Speicher- und CPU-Zyklen.

Zum Beispiel kann eine Menge Speicher durch Caching belegt werden, was speziell zur Verbesserung der Leistung dient, da Sie versuchen, eine teure Operation zu vermeiden.

Also denken Sie durch Ihre Datenstrukturen und stellen Sie sicher, dass Sie die Dinge nicht länger im Speicher behalten als Sie müssen. Wenn es sich um eine Web-App handelt, vermeiden Sie es, viele Daten in der Sitzungsvariablen zu speichern. Vermeiden Sie statische Referenzen auf riesige Speicherpools usw.

4

Die erste Schleife ist besser. Da

  • die Variable etwas wird deutlich schneller (theoretisch)
  • das Programm besser zu lesen ist.

Aber vom Punkt des Gedächtnisses ist dies irrelevant.

Wenn Sie Speicherprobleme haben, sollten Sie profilieren, wo es verbraucht wird.

+0

Sorry, bitte korrigieren Sie mich, wenn ich falsch liege, aber die erste Schleife weist Speicher für eine neue Variable auf jeder Schleife zu. Wenn die Schleife 100 Mal ausgeführt wird, wie ist es dann besser, 100 Strings zu erstellen, statt nur denselben zu überschreiben und Speicher für nur 1 String zuzuordnen (wie in der zweiten Schleife)? – Mapisto

+0

Der Speicher auf dem Stapel für alle lokalen Variablen einer Methode wird beim Methodeneintrag zugewiesen und nicht beim Deklarieren von Punkt im Code. Die Größe einer solchen Variablen beträgt 4 Bytes. (möglich 8 Bytes auf 64 Bit VM). Die Größe für den String wird auf dem Heap und nicht auf dem Stapel zugewiesen. Diese Zuordnung erfolgt für das Schlüsselwort "new". Es gibt keinen Unterschied in der Zuordnung der Strings. – Horcrux7

8

In beiden Codebeispielen sind keine Objekte erstellt. Sie legen lediglich einen Objektverweis auf eine Zeichenfolge fest, die sich bereits im ArrayOfStuff befindet. So gibt es in der Erinnerung keinen Unterschied.

1

Nun, die erste Schleife ist eigentlich besser, weil der Umfang von etwas kleiner ist. In Bezug auf Speicherverwaltung - es macht keinen großen Unterschied.

Die meisten Java-Speicherprobleme treten auf, wenn Sie Objekte in einer Sammlung speichern, aber vergessen, sie zu entfernen. Ansonsten macht der GC seine Arbeit ziemlich gut.

1

Das erste Beispiel ist in Ordnung. Es gibt dort keine Speicherzuteilung, abgesehen von einer Stapelvariablenzuordnung und Freigabe von jedem Mal durch die Schleife (sehr billig und schnell).

Der Grund ist, dass all das, was 'zugewiesen' wird, eine Referenz ist, die eine 4 Byte Stapelvariable ist (auf den meisten 32 Bit Systemen sowieso). Eine Stapelvariable wird durch Hinzufügen zu einer Speicheradresse, die die Oberseite des Stapels darstellt, 'zugewiesen' und ist somit sehr schnell und billig.

Was Sie brauchen, von vorsichtig zu sein, ist für Schleifen wie:

for (int i = 0; i < some_large_num; i++) 
{ 
    String something = new String(); 
    //do stuff with something 
} 

als dass tatsächlich Speicher allocatiton tun.

+0

Es gibt nicht einmal die Zuweisung auf dem Stapel für jede Iteration. Diese werden in der Tabelle der lokalen Methodenvariablen (einmal) zugeordnet, die Variable wird jedoch automatisch beim Beenden der Schleife nicht mehr angezeigt. –

+0

Ja, aber bei jeder Iteration wird ein neues String-Objekt erstellt, das teurer ist als eine Stapelzuweisung. – workmad3

5

Die JVM befreit am besten kurzlebige Objekte. Versuchen Sie nicht, Objekte zuzuordnen, die Sie nicht benötigen. Sie können die Speichernutzung jedoch erst optimieren, wenn Sie Ihre Arbeitsauslastung, die Objektlebensdauer und die Objektgrößen verstanden haben. Ein Profiler kann Ihnen das sagen.

Schließlich müssen Sie die # 1 Sache vermeiden: Verwenden Sie niemals Finalizer.Finalizer beeinträchtigen die Garbage Collection, da das Objekt nicht einfach freigegeben werden kann, sondern zur endgültigen Fertigstellung in die Warteschlange gestellt werden muss. Es ist am besten, Finalizer niemals zu verwenden.

Wie für die Speicherauslastung, die Sie in Eclipse sehen, ist es nicht unbedingt relevant. Der GC wird seine Arbeit basierend darauf ausführen, wie viel freier Speicher vorhanden ist. Wenn Sie viel freien Speicher haben, sehen Sie möglicherweise keinen einzigen GC, bevor die App heruntergefahren wird. Wenn Sie feststellen, dass Ihre App nicht mehr über genügend Arbeitsspeicher verfügt, kann Ihnen nur ein echter Profiler mitteilen, wo die Schwachstellen oder Ineffizienzen liegen.

+0

In objektorientierten, Garbage Collection-Sprachen wird die schlechte Leistung oft durch eine große Anzahl von langlebigen Objekten verursacht, besonders wenn viele Kompositionsebenen vorhanden sind. Wenn ich beispielsweise ein Spreadsheet-Objekt mit einem 2D-Array von Cell-Objekten erzeuge, wobei jedes Objekt ein Color-Objekt, Value-Objekt und Formatierungsobjekt enthält und jedes Formatierungsobjekt enthält ..... Nun, Sie haben die Idee. Es wird lange dauern, bis sich der GC mit dem Objektgraphen beschäftigt. Auch die Schichten der Speicherindirektion verursachen Verzögerung (in Smalltalk sowieso. Ich bin mir über Java nicht so sicher). – ahoffer

1

Wenn Sie noch nicht haben, empfehle ich die Installation der Eclipse Test & Performance Tools Platform (TPTP). Wenn Sie den Heapspeicher dumpen und untersuchen möchten, überprüfen Sie die SDK-Tools jmap and jhat. Siehe auch Monitoring and Managing Java SE 6 Platform Applications.

+1

Da TPTP jetzt (5 Jahre später) wirklich tot ist, gehen Sie zu jvisualvm, das mit JDK ausgeliefert wird - und analysieren Sie gc logs (http://www.tagtraum.com/gcviewer.html oder https://github.com)/chewiebug/GCViewer). –

4

Meiner Meinung nach sollten Sie Mikrooptimierungen wie diese vermeiden. Sie kosten viele Gehirnzyklen, haben aber meistens wenig Einfluss.

Ihre Anwendung hat wahrscheinlich ein paar zentrale Datenstrukturen. Das sind diejenigen, über die Sie sich Sorgen machen sollten. Wenn Sie sie beispielsweise ausfüllen, weisen Sie ihnen eine gute Schätzung der Größe zu, um eine wiederholte Größenanpassung der zugrunde liegenden Struktur zu vermeiden. Dies gilt insbesondere für StringBuffer, ArrayList, HashMap und dergleichen. Gestalten Sie Ihren Zugang zu diesen Strukturen gut, damit Sie nicht viel kopieren müssen.

Verwenden Sie die richtigen Algorithmen, um auf die Datenstrukturen zuzugreifen. Auf der untersten Ebene, wie die von Ihnen erwähnte Schleife, verwenden Sie Iterator s, oder vermeiden Sie mindestens .size() die ganze Zeit zu rufen. (Ja, Sie fragen die Liste jedes Mal nach ihrer Größe, die sich meistens nicht ändert.) Übrigens habe ich oft einen ähnlichen Fehler mit Map s gesehen. Menschen iterieren über den keySet() und get Wert, anstatt nur über die entrySet() zu iterieren. Der Speichermanager wird Ihnen für die zusätzlichen CPU-Zyklen danken.

2

Wie ein Poster oben vorgeschlagen, verwenden Sie Profiler, um die Speicher (und/oder CPU) Nutzung bestimmter Teile Ihres Programms zu messen, anstatt zu versuchen, es zu erraten. Sie können überrascht sein, was Sie finden!

Es gibt auch einen zusätzlichen Vorteil. Sie werden mehr über Ihre Programmiersprache und Ihre Anwendung verstehen.

Ich benutze VisualVM für Profilerstellung und empfehle es sehr. Es kommt mit jdk/jre Verteilung.

0

"Bin ich falsch zu sagen, dass die untere Schleife ist besser?", Ist die Antwort NEIN, ist nicht nur besser, im gleichen Fall ist notwendig ... Die variable Definition (nicht der Inhalt), wird im Speicher gemacht Heap, und ist begrenzt, im ersten Beispiel, jede Schleife erstellen Sie eine Instanz in diesem Speicher, und wenn die Größe von "ArrayOfStuff" groß ist, kann "Nicht genügend Speicherfehler: Java-Heap-Speicherplatz" ....

0

Von dem, was ich verstehe Sie sehen, ist die untere Schleife nicht besser. Der Grund dafür ist, dass selbst wenn Sie versuchen, einen einzelnen Verweis (Ex-something) wiederzuverwenden, das Objekt (Ex-arrayOfStuff.get (i)) immer noch aus der Liste referenziert wird (arrayOfStuff). Damit die Objekte für die Sammlung infrage kommen, sollten sie von keinem Ort aus referenziert werden. Wenn Sie sich nach diesem Zeitpunkt über die Lebensdauer der Liste sicher sind, können Sie die Objekte innerhalb einer separaten Schleife entfernen/freigeben.

Die Optimierung, die aus einer statischen Perspektive durchgeführt werden kann (dh keine Änderung an dieser Liste von irgendeinem anderen Thread), ist es besser, den Aufruf von size() wiederholt zu vermeiden. Wenn Sie nicht erwarten, dass sich die Größe ändert, warum berechnen Sie es dann immer wieder neu; Schließlich ist es keine array.length, es ist list.size().