it-swarm.com.de

Was sind einige Best Practices für die Java-Speicherverwaltung?

Ich übernehme einige Anwendungen von einem früheren Entwickler. Wenn ich die Anwendungen über Eclipse ausführe, werden die Speichernutzung und die Größe des Heapspeichers erheblich erhöht. Bei weiteren Nachforschungen sehe ich, dass sie ein Objekt immer wieder in einer Schleife erstellen, und auch andere Dinge.

Ich fing an, aufzuräumen. Aber je mehr ich durchgemacht habe, desto mehr Fragen hatte ich: "Tut das überhaupt etwas?"

Anstatt beispielsweise eine Variable außerhalb der oben genannten Schleife zu deklarieren und ihren Wert in der Schleife festzulegen, 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);
    ...
}

versus

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 besser ist? Vielleicht irre ich mich.

Und was ist nach der zweiten Schleife oben, wenn ich "etwas" auf null zurücksetze? Würde das etwas Gedächtnis löschen?

Welche bewährten Methoden für die Speicherverwaltung kann ich befolgen, um die Speichernutzung in meinen Anwendungen gering zu halten?

Update:

Ich freue mich über jedes Feedback. Ich habe jedoch nicht wirklich nach den obigen Schleifen gefragt (obwohl ich nach Ihrem Rat zur ersten Schleife zurückgekehrt bin). Ich versuche, einige bewährte Methoden zu finden, auf die ich achten kann. Etwas in der Art von "Wenn Sie mit der Verwendung einer Sammlung fertig sind, bereinigen Sie sie". Ich muss nur wirklich sicherstellen, dass diese Anwendungen nicht so viel Speicher belegen.

37
Ascalonian

Versuchen Sie nicht, die VM zu überlisten. Die erste Schleife ist die empfohlene Best Practice, sowohl hinsichtlich der Leistung als auch der Wartungsfreundlichkeit. Wenn Sie den Verweis nach der Schleife wieder auf null setzen, ist keine sofortige Speicherfreigabe garantiert. Der GC erledigt seine Aufgabe am besten, wenn Sie den minimal möglichen Umfang verwenden.

Bücher, die diese Dinge ausführlich behandeln (aus Benutzersicht), sind Effective Java 2 und Implementierungsmuster .

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

37
cherouvim

Diese beiden Schleifen sind bis auf den Geltungsbereich von something gleichwertig. siehe diese Frage für Details.

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 damit fertig sind. Und oh ja, "Messen Sie nicht, raten Sie nicht." Verwenden Sie einen Profiler, um zu sehen, wo der Speicher zugewiesen wird.

9
Michael Myers

In beiden Codebeispielen wurden keine Objekte erstellt. Sie legen lediglich einen Objektverweis auf eine Zeichenfolge fest, die sich bereits im arrayOfStuff befindet. Gedächtnismäßig gibt es also keinen Unterschied.

8
Kees de Kooter

Die JVM befreit am besten kurzlebige Objekte. Versuchen Sie nicht, Objekte zuzuordnen, die Sie nicht benötigen. Sie können die Speicherauslastung jedoch erst optimieren, wenn Sie Ihre Workload, die Objektlebensdauer und die Objektgrößen kennen. Ein Profiler kann Ihnen dies sagen.

Die Nummer 1, die Sie unbedingt vermeiden sollten: Verwenden Sie niemals Finalizer. Finalizer beeinträchtigen die Speicherbereinigung, da das Objekt nicht einfach freigegeben werden kann, sondern für die Finalisierung in die Warteschlange gestellt werden muss. Dies kann vorkommen oder nicht. Es ist am besten, niemals Finalizer zu verwenden.

Die Speicherbelegung, die Sie in Eclipse sehen, ist nicht unbedingt relevant. Der GC erledigt seine Arbeit, je nachdem, wie viel Speicher frei ist. Wenn Sie über viel freien Speicher verfügen, wird möglicherweise kein einzelner GC angezeigt, bevor die App heruntergefahren wird. Wenn Sie feststellen, dass Ihre App über genügend Arbeitsspeicher verfügt, kann Ihnen nur ein echter Profiler sagen, wo die Lecks oder Ineffizienzen liegen.

Die beiden Schleifen benötigen im Wesentlichen die gleiche Menge an Speicher, jeder Unterschied wäre vernachlässigbar. "Zeichenfolge etwas" erstellt nur einen Verweis auf ein Objekt, nicht ein neues Objekt in sich selbst, und der zusätzliche Speicher ist daher klein. Außerdem wird der Compiler/in Kombination mit JVM den generierten Code wahrscheinlich trotzdem optimieren. 

Bei der Speicherverwaltung sollten Sie wirklich versuchen, Ihr Gedächtnis besser zu verstehen, um die tatsächlichen Engpässe zu verstehen. Suchen Sie vor allem nach statischen Referenzen, die auf einen großen Speicherplatz verweisen, da dieser nie gesammelt wird. 

Sie können auch Weak References und andere spezialisierte Speicherverwaltungsklassen anzeigen. 

Denken Sie schließlich daran, dass, wenn eine Anwendung Speicher beansprucht, ein Grund dafür vorliegen könnte ...

Update Der Schlüssel zur Speicherverwaltung sind Datenstrukturen sowie die benötigte Leistung/wann. Der Kompromiss liegt oft zwischen Speicher- und CPU-Zyklen. 

Zum Beispiel kann viel Speicherplatz durch Zwischenspeicherung belegt werden. Dies dient insbesondere der Verbesserung der Leistung, da Sie versuchen, einen kostspieligen Vorgang zu vermeiden. 

Überlegen Sie sich also Ihre Datenstrukturen und stellen Sie sicher, dass Sie die Dinge nicht länger im Gedächtnis behalten, als Sie müssen. Wenn es sich um eine Web-App handelt, sollten Sie nicht viele Daten in der Sitzungsvariablen speichern. Vermeiden Sie statische Verweise auf große Speicherpools usw. 

5
Jean Barmash

Die erste Schleife ist besser. weil

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

Aber vom Erinnerungspunkt ist dies irrelevant.

Wenn Sie Probleme mit dem Speicher haben, sollten Sie angeben, wo er verbraucht wird.

4
Horcrux7

Meiner Meinung nach sollten Sie solche Mikrooptimierungen vermeiden. Sie kosten viele Gehirnzyklen, haben aber meist nur geringe Auswirkungen.

Ihre Anwendung hat wahrscheinlich einige zentrale Datenstrukturen. Das sind die, um die Sie sich Sorgen machen sollten. Wenn Sie sie beispielsweise vorab mit einer guten Schätzung der Größe füllen, um eine wiederholte Größenänderung der zugrunde liegenden Struktur zu vermeiden. Dies gilt insbesondere für StringBuffer, ArrayList, HashMap und dergleichen. Gestalten Sie Ihren Zugang zu diesen Strukturen gut, so dass Sie nicht viel kopieren müssen.

Verwenden Sie die richtigen Algorithmen, um auf die Datenstrukturen zuzugreifen. Verwenden Sie auf der untersten Ebene wie die von Ihnen erwähnte Schleife Iterators, oder vermeiden Sie es, die ganze Zeit .size() aufzurufen. (Ja, Sie fragen die Liste jedes Mal nach der Größe, die sich meistens nicht ändert.) Übrigens, ich habe oft einen ähnlichen Fehler mit Maps gesehen. Die Benutzer durchlaufen die Werte keySet() und get, anstatt sie einfach nur über die entrySet() zu iterieren. Der Speichermanager wird sich für die zusätzlichen CPU-Zyklen bedanken.

4
Ronald Blaschke

Verwenden Sie als oben vorgeschlagenes Poster den Profiler, um die Speicherauslastung (und/oder CPU-Auslastung) 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 verstehen mehr über Ihre Programmiersprache und Ihre Anwendung.

Ich verwende VisualVM zur Profilerstellung und empfehle es sehr. Es kommt mit Jdk/Jre-Verteilung.

2
riz

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

Die meisten Java-Speicherprobleme treten auf, wenn Sie Objekte in einer Sammlung speichern. Vergessen Sie jedoch nicht, sie zu entfernen. Ansonsten macht der GC seinen Job ganz gut.

1
siddhadev

Das erste Beispiel ist gut. Dort gibt es keine Speicherzuweisung, abgesehen von der Zuweisung und Freigabe von Stack-Variablen jedes Mal durch die Schleife (sehr billig und schnell). 

Der Grund ist, dass alles, was "zugewiesen" wird, eine Referenz ist, die eine 4-Byte-Stackvariable ist (auf den meisten 32-Bit-Systemen sowieso). Eine Stack-Variable wird durch das Hinzufügen einer Speicheradresse, die die oberste Ebene des Stacks darstellt, "zugewiesen" und ist daher sehr schnell und kostengünstig.

Worauf Sie achten müssen, ist für Schleifen wie:

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

denn das macht eigentlich Speicherzuordnung.

1
workmad3

Wenn Sie dies noch nicht getan haben, empfehle ich die Installation der Eclipse Test & Performance Tools-Plattform (TPTP). Wenn Sie den Heapspeicher ausgeben und untersuchen möchten, überprüfen Sie das SDK jmap und die Jhat -Tools. Siehe auch Überwachen und Verwalten von Java SE 6-Plattformanwendungen .

1
McDowell

"Bin ich falsch zu sagen, dass die Bottom-Loop besser ist?", Die Antwort ist NEIN, ist nicht nur besser, in demselben Fall ist es notwendig ... Die Variablendefinition (nicht der Inhalt) wird im Memory-Heap gemacht und ist begrenzt, im ersten Beispiel erstellt jede Schleife eine Instanz in diesem Speicher, und wenn die Größe von "arrayOfStuff" groß ist, kann ein "Nicht genügend Speicherfehler: Java-Heap-Speicherplatz" auftreten.

0
Eduard

Nach allem, was ich verstehe, ist die untere Schleife nicht besser. Der Grund ist, auch wenn Sie versuchen, einen einzelnen Verweis (Ex-Objekt) wiederzuverwenden. Tatsache ist, dass das Objekt (Ex - arrayOfStuff.get (i)) immer noch aus der Liste (arrayOfStuff) referenziert wird. Damit die Objekte für die Sammlung geeignet sind, sollten sie an keiner Stelle referenziert werden. Wenn Sie sicher sind, dass die Liste nach diesem Zeitpunkt noch gültig ist, können Sie die Objekte innerhalb einer separaten Schleife entfernen. 

Bei der Optimierung, die aus einer statischen Perspektive durchgeführt werden kann (dh es werden keine Änderungen an dieser Liste von einem anderen Thread vorgenommen), ist es besser, den Aufruf von size () wiederholt zu vermeiden. Wenn Sie also nicht erwarten, dass sich die Größe ändert, warum sollten Sie sie immer wieder berechnen? es ist schließlich kein array.length, es ist list.size ().

0
Narita