Erstellt ein Lambda-Ausdruck bei jeder Ausführung ein Objekt auf dem Heap?

181

Wenn ich eine Sammlung mit dem neuen syntaktischen Zucker von Java 8 durchlaufe, wie z

myStream.forEach(item -> {
  // do something useful
});

Entspricht dies nicht dem folgenden "alten Syntax" -Schnipsel?

myStream.forEach(new Consumer<Item>() {
  @Override
  public void accept(Item item) {
    // do something useful
  }
});

Bedeutet dies, dass Consumerjedes Mal, wenn ich eine Sammlung durchlaufe , ein neues anonymes Objekt auf dem Heap erstellt wird? Wie viel Speicherplatz benötigt dies? Welche Auswirkungen auf die Leistung hat es? Bedeutet das, dass ich beim Durchlaufen großer mehrstufiger Datenstrukturen lieber den alten Stil für Schleifen verwenden sollte?

Bastian Voigt
quelle
47
Kurze Antwort: nein. Für zustandslose Lambdas (diejenigen, die nichts aus ihrem lexikalischen Kontext erfassen) wird immer nur eine Instanz erstellt (träge) und am Erfassungsort zwischengespeichert. (So ​​funktioniert die Implementierung; die Spezifikation wurde sorgfältig geschrieben, um diesen Ansatz zu ermöglichen, aber nicht zu erfordern.)
Brian Goetz

Antworten:

157

Es ist gleichwertig, aber nicht identisch. Einfach gesagt, wenn ein Lambda-Ausdruck keine Werte erfasst, ist er ein Singleton, der bei jedem Aufruf wiederverwendet wird.

Das Verhalten ist nicht genau angegeben. Der JVM wird große Freiheit bei der Implementierung eingeräumt. Derzeit erstellt die JVM von Oracle (mindestens) eine Instanz pro Lambda-Ausdruck (dh teilt die Instanz nicht zwischen verschiedenen identischen Ausdrücken), sondern erstellt Singletons für alle Ausdrücke, die keine Werte erfassen.

Sie können diese Antwort für weitere Details lesen . Dort gab ich nicht nur eine detailliertere Beschreibung, sondern testete auch Code, um das aktuelle Verhalten zu beobachten.


Dies wird in der Java®-Sprachspezifikation, Kapitel „ 15.27.4. Laufzeitauswertung von Lambda-Ausdrücken

Zusammenfassend:

Diese Regeln sollen Implementierungen der Java-Programmiersprache Flexibilität bieten, indem:

  • Ein neues Objekt muss nicht bei jeder Bewertung zugewiesen werden.

  • Objekte, die durch verschiedene Lambda-Ausdrücke erzeugt werden, müssen nicht zu verschiedenen Klassen gehören (wenn die Körper beispielsweise identisch sind).

  • Jedes durch Auswertung erzeugte Objekt muss nicht zur selben Klasse gehören (erfasste lokale Variablen können beispielsweise inline sein).

  • Wenn eine "vorhandene Instanz" verfügbar ist, muss sie nicht bei einer vorherigen Lambda-Bewertung erstellt worden sein (sie wurde möglicherweise beispielsweise während der Initialisierung der einschließenden Klasse zugewiesen).

Holger
quelle
23

Wann eine Instanz, die das Lambda darstellt, sensibel erstellt wird, hängt vom genauen Inhalt des Körpers Ihres Lambda ab. Der Schlüsselfaktor ist nämlich, was das Lambda aus der lexikalischen Umgebung erfasst . Wenn kein Status erfasst wird, der von Erstellung zu Erstellung variabel ist, wird nicht jedes Mal eine Instanz erstellt, wenn die for-each-Schleife eingegeben wird. Stattdessen wird zur Kompilierungszeit eine synthetische Methode generiert, und die Lambda-Verwendungssite empfängt nur ein Singleton-Objekt, das an diese Methode delegiert.

Beachten Sie außerdem, dass dieser Aspekt von der Implementierung abhängt und Sie mit zukünftigen Verbesserungen und Weiterentwicklungen von HotSpot in Richtung höherer Effizienz rechnen können. Es gibt allgemeine Pläne, z. B. ein leichtes Objekt ohne eine vollständige entsprechende Klasse zu erstellen, die gerade genug Informationen enthält, um an eine einzelne Methode weitergeleitet zu werden.

Hier ist ein guter, zugänglicher ausführlicher Artikel zum Thema:

http://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood

Marko Topolnik
quelle
0

Sie übergeben eine neue Instanz an die forEachMethode. Jedes Mal, wenn Sie dies tun, erstellen Sie ein neues Objekt, jedoch nicht eines für jede Schleifeniteration. Die Iteration erfolgt innerhalb der forEachMethode mit derselben 'Callback'-Objektinstanz, bis sie mit der Schleife durchgeführt wird.

Der von der Schleife verwendete Speicher hängt also nicht von der Größe der Sammlung ab.

Entspricht das nicht dem 'alten Syntax'-Snippet?

Ja. Es hat leichte Unterschiede auf einem sehr niedrigen Niveau, aber ich denke nicht, dass Sie sich um sie kümmern sollten. Lamba-Ausdrücke verwenden die Funktion invokedynamic anstelle von anonymen Klassen.

Aalku
quelle
Haben Sie eine Dokumentation, die dies spezifiziert? Es ist ziemlich die interessante Optimierung.
A. Rama
Vielen Dank, aber was ist, wenn ich eine Sammlung von Sammlungen von Sammlungen habe, beispielsweise wenn ich eine Tiefensuche in einer Baumdatenstruktur durchführe?
Bastian Voigt
2
@ A.Rama Sorry, ich sehe die Optimierung nicht. Es ist dasselbe mit oder ohne Lambdas und mit oder ohne forEach-Schleife.
Aalku
1
Es ist nicht wirklich dasselbe, aber es wird immerhin höchstens ein Objekt pro Verschachtelungsebene benötigt, was vernachlässigbar ist. Bei jeder neuen Iteration einer inneren Schleife wird ein neues Objekt erstellt, das höchstwahrscheinlich das aktuelle Element aus der äußeren Schleife erfasst. Dies erzeugt einen gewissen GC-Druck, aber es gibt immer noch nichts, worüber man sich wirklich Sorgen machen müsste.
Marko Topolnik
4
@aalku: " Jedes Mal, wenn Sie dies tun, erstellen Sie ein neues Objekt ": Nicht gemäß dieser Antwort von Holger und diesem Kommentar von Brian Goetz .
Lii