Erkennen und Entfernen (während einer Sitzung) unbenutzter @ViewScoped-Beans, die nicht mit Garbage Collection gesammelt werden können

8

EDIT: Das von dieser Frage aufgeworfene Problem wird in diesem Artikel von codebulb.ch sehr gut erklärt und bestätigt, einschließlich einiger Vergleiche zwischen JSF @ViewScoped , CDI @ViewSCoped und den Omnifaces @ViewScoped und einer klaren Aussage JSF @ViewScoped ist "undicht": 24. Mai 2015 Java EE 7 Bean Scopes verglichen Teil 2 von 2

EDIT: 2017-12-05 Der Testfall, der für diese Frage verwendet wird, ist immer noch sehr nützlich, jedoch basieren die Schlussfolgerungen bezüglich der Garbage Collection im ursprünglichen Beitrag (und den Bildern) auf JVisualVM, und ich habe sie seitdem gefunden sind nicht gültig. Verwenden Sie stattdessen den NetBeans Profiler! Ich erhalte jetzt vollständig konsistente Ergebnisse für OmniFaces ViewScoped mit der Test-App zum Erzwingen von GC aus dem NetBeans Profiler anstelle von JVisualVM an GlassFish / Payara, wo ich immer noch Referenzen bekomme gehalten (auch nachdem @PreDestroy aufgerufen wurde) nach Feld sessionListeners vom Typ com.sun.web.server.WebContainerListener in ContainerBase$ContainerBackgroundProcessor , und sie werden nicht GC.

Es ist bekannt, dass in JSF2.2 für eine Seite, die eine @ViewScoped-Bean verwendet, das Navigieren weg von ihr (oder das erneute Laden) mit einer der folgenden Techniken dazu führt, dass Instanzen der @ViewScoped-Bohne "baumeln" die Sitzung, so dass es nicht Müll gesammelt wird, was zu endlos wachsenden Heap-Speicher führt (solange provoziert von GETs):

  • Verwenden Sie einen h: Link, um eine neue Seite abzurufen.

  • Verwenden Sie einen h: outputLink (oder ein HTML A-Tag), um eine neue Seite abzurufen.

  • Erneutes Laden der Seite im Browser mit einem RELOAD-Befehl oder einer Schaltfläche.

  • Erneutes Laden der Seite mit der Tastatur ENTER auf der URL des Browsers (auch ein GET).

Im Gegensatz dazu führt das Durchlaufen des JSF-Navigationssystems mit say an h: commandButton dazu, dass die @ViewScoped-Bean freigegeben wird, sodass sie als Garbage Collected erfasst werden kann.

Dies wird erklärt (von BalusC) bei JSF 2.1 ViewScopedBean @PreDestroy-Methode wird nicht aufgerufen und für JSF2.2 und Mojarra 2.2.9 von meinem kleinen NetBeans-Beispielprojekt unter Ссылка , welches Projekt die verschiedenen Navigationsfälle veranschaulicht und verfügbar ist zum Herunterladen hier . (EDIT: 2015-05-28: Der vollständige Code ist jetzt auch hier verfügbar.)

[EDIT: 13.11.2016 Es gibt jetzt auch eine verbesserte Test-Web-App mit vollständigen Anleitungen und Vergleichen mit OmniFaces @ViewScoped und der Ergebnistabelle auf GitHub hier: Ссылка

Ich wiederhole hier ein Bild der index.html, die die Navigationsfälle und die Ergebnisse für den Heap-Speicher zusammenfasst:

F: Wie kann ich solche "hängende / hängende" @ViewScoped-Beans erkennen, die durch GET-Navigationen verursacht werden, und diese entfernen oder anderweitig als Garbage Collectable rendern?

Bitte beachten Sie, dass ich nicht frage, wie ich sie bereinigen soll, wenn die Sitzung beendet wird. Ich habe bereits verschiedene Lösungen dafür gefunden. Ich suche nach Möglichkeiten, sie während einer Sitzung zu bereinigen, so dass der Heapspeicher ausreicht nicht übermäßig während einer Sitzung aufgrund versehentlicher GET-Navigationen.

Webel IT Australia - upvoter 23.05.2015, 08:30
quelle

2 Antworten

5

Grundsätzlich möchten Sie, dass der JSF-Ansichtszustand und alle für den Sichtbereich festgelegten Beans während des Entladens eines Fensters zerstört werden. Die Lösung wurde implementiert in OmniFaces @ViewScoped Anmerkung , die wie unten in seiner Dokumentation fleshed ist aus:

  

Es kann Fälle geben, in denen es wünschenswert ist, eine View-Scoped-Bean sofort zu zerstören, wenn der Browser unload event aufgerufen wird. I.e. wenn der Benutzer mit GET weg navigiert, oder schließt die Browser-Registerkarte / Fenster. Keines der beiden JSF 2.2-Scope-Annotations unterstützt dies. Seit OmniFaces 2.2 garantiert diese CDI-Ansichtsbereichsanmerkung, dass die Methode @PreDestroy mit Annotationen auch beim Entladen des Browsers aufgerufen wird. Dieser Trick wird durch eine synchrone XHR-Anfrage über ein automatisch eingebundenes Hilfsskript omnifaces:unload.js erledigt. Es gibt jedoch eine kleine Einschränkung: Bei langsamem Netzwerk und / oder schlechter Server-Hardware kann es zu einer merklichen Verzögerung zwischen der Endbenutzeraktion beim Entladen der Seite und dem gewünschten Ergebnis kommen. Wenn dies unerwünscht ist, dann halte dich besser an die eigenen Sichtbereichsanmerkungen von JSF 2.2 und akzeptiere die verschobene Zerstörung.

     

Seit OmniFaces 2.3 haben die Entlastungs worden weiter verbessern auch physisch den zugehörigen JSF Ansichtszustand von JSF-Implementierung interner LRU Karte bei serverseitigen Zustand Einsparung zu entfernen, hierdurch das Risikos bei ViewExpiredException auf den anderen Ansichten Verringerung des wurden früher erstellt / geöffnet. Als Nebeneffekt dieser Änderung scoped, die @PreDestroy kommentierten Methode von jeder Standard-JSF Ansicht Bohnen in der gleichen Ansicht wie die referenzierte OmniFaces CDI Ansicht bean scoped wird auch auf Browser Entladen aufgerufen wird, garantiert.

Den entsprechenden Quellcode finden Sie hier:

Das Entlade-Skript wird ausgeführt während des beforeunload -Ereignisses des -Fensters, es sei denn, wird durch eine JSF-basierte (ajax) Formularübertragung verursacht. Was commandlink und / oder ajax betrifft, ist dies implementierungsspezifisch. Derzeit Mojarra, MyFaces und PrimeFaces sind anerkannt.

Das Entlade-Skript wird Trigger navigator.sendBeacon auf modernen Browsern und Herbst Zurück zum synchronen XHR (asynchroner Vorgang würde fehlschlagen, da die Seite früher entladen wird, als die Anforderung tatsächlich den Server erreicht).

%Vor%

Der Handler zum Entladen der Ansicht wird Explizit zerstöre alle @ViewScoped Beans, einschließlich Standard-JSFs (beachte, dass das Entlade-Skript nur dann initialisiert wird, wenn die View auf mindestens eine OmniFaces @ViewScoped -Bohne verweist).

%Vor%

Dies zerstört jedoch nicht den physischen JSF-Ansichtszustand in der HTTP-Sitzung und somit den folgenden Anwendungsfall würde fehlschlagen:

  1. Setzen Sie die Anzahl der physischen Ansichten auf 3 (in Mojarra verwenden Sie com.sun.faces.numberOfLogicalViews context param und in MyFaces verwenden Sie org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION context param).
  2. Erstellen Sie eine Seite, die auf eine standardmäßige JSF @ViewScoped -Bohne verweist.
  3. Öffnen Sie diese Seite in einem Tab und halten Sie sie immer geöffnet.
  4. Öffnen Sie dieselbe Seite in einem anderen Tab und schließen Sie dann sofort diesen Tab.
  5. Öffnen Sie dieselbe Seite in einem anderen Tab und schließen Sie dann sofort diesen Tab.
  6. Öffnen Sie dieselbe Seite in einem anderen Tab und schließen Sie dann sofort diesen Tab.
  7. Übermitteln Sie ein Formular auf der ersten Registerkarte.

Dies würde mit ViewExpiredException fehlschlagen, weil die JSF-Ansichtszustände von zuvor geschlossenen Tabs während PreDestroyViewMapEvent nicht physisch zerstört werden. Sie bleiben immer noch in der Sitzung. OmniFaces @ViewScoped wird sie tatsächlich zerstören. Das Zerstören des JSF-Ansichtszustands ist jedoch implementierungsspezifisch. Das erklärt zumindest den ziemlich Hacky-Code in Hacks klasse, der das erreichen sollte.

Der Integrationstest für diesen speziellen Fall kann in ViewScopedIT#destroyViewState() auf ViewScopedIT.xhtml was läuft derzeit gegen WildFly 10.0.0, TomEE 7.0.1 und Payara 4.1.1.163.

Kurz gesagt: Ersetzen Sie einfach javax.faces.view.ViewScoped durch org.omnifaces.cdi.ViewScoped . Der Rest ist transparent.

%Vor%

Ich habe mich zumindest bemüht schlägt eine öffentliche API-Methode vor, um den JSF-Ansichtszustand physisch zu zerstören. Vielleicht wird es in JSF 2.3 kommen, und dann sollte ich in der Lage sein, den Textbaustein in der Klasse OmniFaces Hacks zu eliminieren. Sobald das Ding in OmniFaces poliert ist, wird es vielleicht letztendlich in JSF kommen, aber nicht vor 2.4.

    
BalusC 11.11.2016, 16:23
quelle
2

Okay, also habe ich etwas zusammengeschustert.

Das Prinzip

Die nun irrelevanten Viewscoped-Beans sitzen dort und verschwenden Zeit und Speicherplatz für jeden, denn in einem GET-Navigationsfall ist der Server mit einem der von Ihnen hervorgehobenen Steuerelemente nicht beteiligt. Wenn der Server nicht beteiligt ist, hat er keine Möglichkeit zu wissen, dass die Viewscoped-Beans jetzt redundant sind (das heißt, bis die Sitzung beendet ist). Also, was hier benötigt wird, ist eine Möglichkeit, der Serverseite mitzuteilen, dass die Ansicht, von der aus Sie navigieren, ihre View-Scoped-Beans beenden muss

Die Einschränkungen

Die Server-Seite sollte benachrichtigt werden, sobald die Navigation stattfindet

  1. beforeunload oder unload in <h:body/> wäre ideal gewesen, aber für die folgenden Probleme

    • Browser respektieren keines der beiden einheitlich

    • Eine Lösung, die eine der beiden Methoden verwendet, wird höchstwahrscheinlich eine AJAX-Lösung erfordern, die das JSF-Framework verlässt. JSFs Ajax-ready-Skript muss im Kontext eines Formulars ausgeführt werden. Sie können nicht <h:body/> in einem Formular haben. Ich bevorzuge es, alles in JSF zu behalten

  2. Sie können keine Ajax-Anforderung in onclick eines Steuerelements senden und auch im selben Steuerelement navigieren. Nicht ohne ein schmutziges Popup sowieso. Das Navigieren von onclick in h:button oder h:link ist also nicht möglich.

Der schmutzige Kompromiss

Leiten Sie eine Ajax-Anfrage onclick ein, und haben Sie eine PhaseListener die eigentliche Navigation und die Viewscope-Bereinigung

Das Rezept

  1. 1 PhaseListener (a ViewHandler würde auch hier funktionieren; ich gehe mit ersterem, weil es einfacher zu installieren ist)

  2. 1 Wrapper um die JSF js API

  3. Ein Mittel zur Schande

Mal sehen:

  1. Die PhaseListener

    %Vor%
  2. Der JS-Wrapper

    %Vor%
  3. Zusammenfügen

    %Vor%

Zu tun

  1. Erweitern Sie die h:link , fügen Sie möglicherweise ein Attribut hinzu, um das Ausgleichsverhalten zu konfigurieren

  2. Die Art und Weise, wie die Ziel-URL übergeben wird, ist verdächtig. könnte ein Loch öffnen

kolossus 24.05.2015 21:40
quelle