Heim > Java > javaLernprogramm > Hauptteil

So reduzieren Sie den Aufwand für die Java-Garbage-Collection

PHPz
Freigeben: 2023-05-04 14:31:06
nach vorne
657 Leute haben es durchsucht

Tipp Nr. 1: Sagen Sie die Kapazität der Sammlung voraus

Alle Standard-Java-Sammlungen, einschließlich benutzerdefinierter und erweiterter Implementierungen (wie Trove und Guava von Google), verwenden unter der Haube Arrays (entweder native Datentypen oder objektbasierte Typen). Da die Größe eines Arrays unveränderlich ist, sobald es zugewiesen ist, führt das Hinzufügen von Elementen zur Sammlung in den meisten Fällen dazu, dass ein neues Array mit großer Kapazität erneut beantragt werden muss, um das alte Array zu ersetzen (bezogen auf das von der Sammlung verwendete Array). zugrunde liegende Implementierung der Sammlung).

Auch wenn keine Größe für die Sammlungsinitialisierung angegeben wird, versuchen die meisten Sammlungsimplementierungen, die Verarbeitung der Neuzuweisung des Arrays zu optimieren und den Overhead auf ein Minimum zu reduzieren. Die besten Ergebnisse können jedoch erzielt werden, wenn beim Aufbau der Sammlung die Größe angegeben wird.

Lassen Sie uns den folgenden Code als einfaches Beispiel analysieren:

öffentliche statische Liste umgekehrt(Liste & lt; ? erweitert T & gt; Liste) {

Listenergebnis = new ArrayList();

for (int i = list.size() - 1; i & gt; = 0; i--) {

result.add(list.get(i));

}

Rückgabeergebnis;

}

Diese Methode weist ein neues Array zu und füllt es dann mit Elementen aus einer anderen Liste, jedoch in umgekehrter Reihenfolge.

Diese Verarbeitungsmethode kann hohe Leistungseinbußen verursachen, und der Optimierungspunkt ist die Codezeile, die Elemente zu einer neuen Liste hinzufügt. Wenn jedes Element hinzugefügt wird, muss die Liste sicherstellen, dass das zugrunde liegende Array über genügend Platz für das neue Element verfügt. Ist ein freier Steckplatz vorhanden, wird das neue Element einfach im nächsten freien Steckplatz abgelegt. Wenn nicht, wird ein neues zugrunde liegendes Array zugewiesen, der alte Array-Inhalt wird in das neue Array kopiert und die neuen Elemente werden hinzugefügt. Dies führt dazu, dass das Array mehrmals zugewiesen wird und die verbleibenden alten Arrays schließlich vom GC zurückgefordert werden.

Wir können diese redundanten Zuweisungen vermeiden, indem wir dem zugrunde liegenden Array beim Erstellen der Sammlung mitteilen, wie viele Elemente es speichern wird öffentliche statische Liste umgekehrt(Liste & lt; ? erweitert T & gt; Liste) {

Listenergebnis = new ArrayList(list.size());

for (int i = list.size() - 1; i & gt; = 0; i--) {

result.add(list.get(i));

}

Rückgabeergebnis;

}

Der obige Code gibt über den Konstruktor von ArrayList einen ausreichend großen Speicherplatz zum Speichern von list.size()-Elementen an und vervollständigt die Zuweisung während der Initialisierung, was bedeutet, dass List während des Iterationsprozesses keinen erneuten Speicherzuweisungen vornehmen muss.

Die Sammlungsklasse von Guava geht noch einen Schritt weiter und ermöglicht es Ihnen, beim Initialisieren der Sammlung explizit die Anzahl der erwarteten Elemente oder einen vorhergesagten Wert anzugeben.

1

2List result = Lists.newArrayListWithCapacity(list.size());

Listenergebnis = Lists.newArrayListWithExpectedSize(list.size());

Im obigen Code wird Ersteres verwendet, wenn wir bereits genau wissen, wie viele Elemente die Sammlung speichern wird, während Letzteres auf eine Weise zugewiesen wird, die falsche Schätzungen berücksichtigt.

Tipp #2: Behandeln Sie den Datenstrom direkt

Beim Umgang mit Datenströmen, etwa dem Lesen von Daten aus einer Datei oder dem Herunterladen von Daten aus dem Netzwerk, kommt der folgende Code sehr häufig vor:

1byte[] fileData = readFileToByteArray(new File("myfile.txt"));

Das resultierende Byte-Array kann als XML-Dokument, JSON-Objekt oder protokollgepufferte Nachricht analysiert werden, wobei einige allgemeine Optionen verfügbar sind.

Der obige Ansatz ist unklug, wenn es um große Dateien oder Dateien mit unvorhersehbaren Größen geht, da OutOfMemoryErrors auftreten, wenn die JVM keinen Puffer für die Verarbeitung der tatsächlichen Datei zuweisen kann.

Auch wenn die Größe der Daten überschaubar ist, verursacht die Verwendung des oben genannten Musters dennoch einen enormen Mehraufwand bei der Speicherbereinigung, da dadurch ein sehr großer Bereich im Heap zum Speichern der Dateidaten reserviert wird.

Eine bessere Möglichkeit, damit umzugehen, besteht darin, einen geeigneten InputStream (wie in diesem Beispiel FileInputStream) zu verwenden, um ihn direkt an den Parser zu übergeben, anstatt die gesamte Datei auf einmal in ein Byte-Array einzulesen. Alle gängigen Open-Source-Bibliotheken stellen entsprechende APIs bereit, um einen Eingabestream direkt zur Verarbeitung zu akzeptieren, wie zum Beispiel:

FileInputStream fis = new FileInputStream(fileName);

MyProtoBufMessage msg = MyProtoBufMessage.parseFrom(fis);

Tipp #3: Verwenden Sie unveränderliche Objekte

Unveränderlichkeit hat so viele Vorteile. Ich muss nicht einmal ins Detail gehen. Es gibt jedoch einen Vorteil, der sich auf die Garbage Collection auswirkt und der berücksichtigt werden sollte.

Die Eigenschaften eines unveränderlichen Objekts können nach der Erstellung des Objekts nicht mehr geändert werden (das Beispiel hier verwendet Eigenschaften von Referenzdatentypen), wie zum Beispiel:

öffentliche Klasse ObjectPair {

privates Endobjekt zuerst;

privates letztes Objekt Sekunde;

öffentliches Objektpaar (Objekt zuerst, Objekt zweitens) {

this.first = zuerst;

this.second = second;

}

öffentliches Objekt getFirst() {

kehre zuerst zurück;

}

öffentliches Objekt getSecond() {

kehre als Zweiter zurück;

}

}

Durch die Instanziierung der oben genannten Klasse wird ein unveränderliches Objekt erstellt. Alle seine Eigenschaften werden mit „final“ geändert und können nach Abschluss der Konstruktion nicht mehr geändert werden.

Unveränderlichkeit bedeutet, dass alle Objekte, auf die ein unveränderlicher Container verweist, erstellt werden, bevor der Container erstellt wird. Was GC betrifft: Der Container ist mindestens so jung wie die jüngste Referenz, die er enthält. Das bedeutet, dass der GC bei der Garbage Collection in der jungen Generation unveränderliche Objekte überspringt, da sie sich in der alten Generation befinden. Die Sammlung unveränderlicher Objekte wird erst dann abgeschlossen, wenn festgestellt wird, dass auf diese unveränderlichen Objekte kein Objekt in der alten Generation verweist alte Generation.

Weniger zu scannende Objekte bedeuten weniger Scans von Speicherseiten, und weniger Scans von Speicherseiten bedeuten einen kürzeren GC-Lebenszyklus, was auch kürzere GC-Pausen und einen besseren Gesamtdurchsatz bedeutet.

Tipp Nr. 4: Seien Sie vorsichtig bei der Verkettung von Zeichenfolgen

Zeichenfolgen sind wahrscheinlich die am häufigsten verwendete nicht-native Datenstruktur in allen JVM-basierten Anwendungen. Aufgrund des impliziten Overheads und der Benutzerfreundlichkeit ist es jedoch sehr leicht, zum Übeltäter für die Belegung großer Speichermengen zu werden.

Das Problem liegt offensichtlich nicht beim String-Literal, sondern bei der Initialisierung des zugewiesenen Speichers zur Laufzeit. Werfen wir einen kurzen Blick auf ein Beispiel für den dynamischen Aufbau einer Zeichenfolge:

öffentlicher statischer String toString(T[] array) {

String-Ergebnis = „[“;

for (int i = 0; i & lt; array.length; i++) {

result += (array[i] == array ? "this" : array[i]);

if (i & lt; array.length - 1) {

Ergebnis += ", ";

}

}

Ergebnis += "]";

Rückgabeergebnis;

}

Dies scheint eine gute Methode zu sein, bei der ein Array von Zeichen verwendet und eine Zeichenfolge zurückgegeben wird. Dies ist jedoch katastrophal für die Objektspeicherzuweisung.

Es ist schwer, hinter diesem Syntax-Zucker zu blicken, aber die tatsächliche Situation hinter den Kulissen ist folgende:

öffentlicher statischer String toString(T[] array) {

String-Ergebnis = „[“;

for (int i = 0; i & lt; array.length; i++) {

StringBuilder sb1 = neuer StringBuilder(result);

sb1.append(array[i] == array ? "this" : array[i]);

result = sb1.toString();

if (i & lt; array.length - 1) {

StringBuilder sb2 = neuer StringBuilder(result);

sb2.append(", ");

result = sb2.toString();

}

}

StringBuilder sb3 = neuer StringBuilder(result);

sb3.append("]");

result = sb3.toString();

Rückgabeergebnis;

}

Zeichenfolgen sind unveränderlich, was bedeutet, dass sie bei jeder Verkettung selbst nicht geändert werden, sondern der Reihe nach neue Zeichenfolgen zugewiesen werden. Darüber hinaus verwendet der Compiler die Standardklasse StringBuilder, um diese Verkettungsvorgänge auszuführen. Dies ist problematisch, da jede Iteration implizit eine temporäre Zeichenfolge und ein temporäres StringBuilder-Objekt zuweist, um beim Erstellen des Endergebnisses zu helfen.

Der beste Weg besteht darin, die obige Situation zu vermeiden und StringBuilder und direktes Anhängen anstelle des nativen Verkettungsoperators („+“) zu verwenden. Hier ist ein Beispiel:

öffentlicher statischer String toString(T[] array) {

StringBuilder sb = new StringBuilder("[");

for (int i = 0; i & lt; array.length; i++) {

sb.append(array[i] == array ? "this" : array[i]);

if (i & lt; array.length - 1) {

sb.append(", ");

}

}

sb.append("]");

return sb.toString();

}

Hier weisen wir den einzigen StringBuilder am Anfang der Methode zu. Zu diesem Zeitpunkt wurden alle Zeichenfolgen und Listenelemente an einen einzigen StringBuilder angehängt. Verwenden Sie abschließend die Methode toString(), um ihn in einen String umzuwandeln und ihn auf einmal zurückzugeben.

Tipp #5: Verwenden Sie Sammlungen bestimmter nativer Typen

Die Standard-Sammlungsbibliothek von Java ist einfach und unterstützt Generika, was eine semistatische Bindung von Typen bei der Verwendung von Sammlungen ermöglicht. Wenn Sie beispielsweise ein Set erstellen möchten, das nur Zeichenfolgen speichert, oder eine Map, die Map speichert, ist dieser Ansatz großartig.

Das eigentliche Problem entsteht, wenn wir eine Liste zum Speichern des int-Typs oder eine Map zum Speichern des Double-Typs als Wert verwenden möchten. Da Generics keine nativen Datentypen unterstützen, besteht eine andere Möglichkeit darin, stattdessen einen Wrapper-Typ zu verwenden, hier verwenden wir List .

Diese Verarbeitungsmethode ist sehr verschwenderisch, da ein Integer ein vollständiges Objekt ist. Der Header eines Objekts belegt 12 Bytes und die darin verwalteten int-Eigenschaften belegen insgesamt 16 Bytes. Dies verbraucht viermal so viel Platz wie eine Liste von int-Typen, die die gleiche Anzahl an Elementen speichert! Ein schwerwiegenderes Problem ist die Tatsache, dass Integer, da es sich um eine reale Objektinstanz handelt, während der Garbage-Collection-Phase vom Garbage Collector für die Wiederverwertung berücksichtigt werden muss.

Um dies zu bewältigen, nutzen wir die großartige Trove-Sammlungsbibliothek in Takipi. Trove gibt einige generische Besonderheiten zugunsten spezialisierter Sammlungen nativer Typen auf, die speichereffizienter sind. Wir verwenden zum Beispiel die sehr leistungsintensive Map. Es gibt eine weitere spezielle Option in Trove in Form von TIntDoubleMap

TIntDoubleMap map = new TIntDoubleHashMap();

map.put(5, 7.0);

map.put(-1, 9.999);

...

Die zugrunde liegende Implementierung von Trove verwendet Arrays nativer Typen, sodass beim Betrieb von Sammlungen kein Boxing (int->Integer) oder Unboxing (Integer->int) von Elementen erfolgt und keine Objekte gespeichert werden, da die zugrunde liegende Implementierung diese verwendet native Datentypspeicherung.

Das obige ist der detaillierte Inhalt vonSo reduzieren Sie den Aufwand für die Java-Garbage-Collection. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:yisu.com
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage