Sunday, April 15, 2012

"Recycle" Ein Versuch einer Erkärung... Teil1

Die meisten Notesforen sind voll von Fragen, warum Javaagenten die in einer Testumgebung so gut funktioniert haben auf dem Echtserver plötzlich die Performance in den Keller ziehen, oder sogar den Agentmanager oder gleich den ganzen Server crashen. Praktisch in jedem dieser Fälle lässt sich das Problem auf ein Problem mit der Noteseigenheit des recycelns von Notesobjekten zurückführen. Also was ist diese geheimnisvolle recycle() Methode die es für jedes Objekt des Notes Java API gibt und warum ist es so wichtig Sie zu verwenden.

Um recycle zu verstehen, muss man wissen, wie das Notes Java API funktioniert.  In diesem Post gehe ich ausschließlich auf den sogenannten lokalen Zugriff ein. Die andere Art DIIOP (funktioniert meiner Meinung nach nicht stabil und vor allem nicht performant) vergesse ich mal. Lokaler Zugriff heißt dabei nicht, dass man nicht auf den Server zugreifen kann. Es heißt nur, dass auf der Maschine entweder ein Notes Client oder ein Server lokal installiert sein muss, damit man diese Methode verwenden kann.

Die Notes Java API besteht einerseits aus der "Notes.jar" die nur kleine Methodenhüllen enthält, die nichts anderes tun als die entsprechenden Funktion in der nativen C++ Bibliothek nlsxbe.dll aufzurufen. Wie der Name lsx schon andeutet, ist das der selbe Code der auch bei der Verwendung des Notes API in Lotus script verwendet wird. Nun ist es so, dass wenn man ein NotesObjekt instantiert wird im Hintergrund auch immer ein natives C++ Objekt erzeugt. Während das Javaobjekt nur minimale Systemressourcen wie Hauptspeicher und vor allem Zugriffshandels belegt, benötigt das native C++ Objekt wesentlich mehr Ressourcen des Betriebssysstems.

Ein kleines Beispiel:

NotesThread.sinitThread(); //Thread für Noteszugriff initialisieren.
try {
   Session ses = NotesFactory.createSession();
} catch (NotesException e) {
   e.printStackTrace();
}
NotesThread.stermThread(); //Thread für Noteszugriff beenden.



Und schon hat man ein klassisches Speicherloch produziert.


In der Zeile

Session ses=NotesFactory.createSession();

wird sowohl ein leichtgewichtiges Java Objekt als auch ein ziemlich schweres C++ Objekt im Hintergrund erzeugt. Jetzt könnte man einwenden sobald die Ausführung den try catch Block verlässt, gibt es keine Referenz mehr auf "ses" und der Garbage Collector wird sich um die Speicherbereinigung kümmern, wie man es von Standardjava Objekten gewohnt ist. Das macht der GC auch brav, aber er kann  nur das leichtgewichtige Javaobjekt freigeben. Das schwergewichtige C++ Objekt bleibt im Hintergrund bestehen und mangels einer Referenz auf dieses Objekt kann dieses auch nie mehr bereinigt werden. Jetzt könnte man einwenden, dass die IBM die Bereinigung des C++ Objekts in die finalize() Methode jeder Klasse implementieren könnte die sich um die Bereinigung des C++ Objekts kümmert. Ich habe das auch lange nicht verstanden, bis ich mich selber ein wenig mit den Internas der GC von Java beschäfigt habe. Ohne zu tief ins Detail zu gehen sollte man sich auf keinen Fall auf finalize verlassen. Laut JVM Spezifikation kann die JVM den Aufruf von finalize auf Später verschieben, bzw. ist es nicht mal Pflicht die finalizer bei Beendigung der JVM aufzurufen. Deshalb hat die IBM diese finalize Methode berechtigterweise nicht verwendet und  bei jeder Klasse des API die recycle Methode implementiert. Beim Aufruf dieser Methode bleibt das leichtgewichtige Javaobjekt erhalten und das native C++ Objekt wird bereinigt.( Das heißt der Speicher wird freigegeben und Zugriffshandels werden geschlossen.) Wichtig ist, dass man ein Notesjavaobjekt nach dem recycle nicht mehr verwenden darf.

Wir müssen also ein ses.recylce() in unseren Code einfügen. Die Frage ist nur Wo?

Falsch wäre es das recycle einfach im Try catch einzubauen. wie z.B.

NotesThread.sinitThread(); //Thread für Noteszugriff initialisieren.
try {

     Session ses = NotesFactory.createSession();
     //Irgendwas mit der Session machen.
     ses.recycle(); //Dies ist ein schwerer Fehler

} catch (NotesException e) {
      e.printStackTrace();

}
NotesThread.stermThread(); //Thread für Noteszugriff beenden.

Das ses.recycle() ist problematisch, da wenn im Codeteil "irgendwas" ein Fehler auftritt in das Catch verzweigt wird und dann das recycle nie ausgeführt wird. Für die verlässliche Ressourcenfreigabe gibt es beim try catch die die finally Klausel.

NotesThread.sinitThread(); //Thread für Noteszugriff initialisieren.
Session ses=null;

try {
     ses = NotesFactory.createSession();
     //Irgendwas mit der Session machen.

     ses.recycle(); //Dies ist ein schwerer Fehler

} catch (NotesException e) {
      e.printStackTrace();

}
} finally {
     if (ses != null) {
         try {
              ses.recycle();
         } catch (NotesException e) {
               e.printStackTrace();
         }
     }

}
NotesThread.stermThread(); //Thread für Noteszugriff beenden.

Nun wird das recycle auf jeden Fall ausgeführt. Egal was im "Irgendwas" Codeteil passiert oder Welche Fehler auftritt das finally wird auf jeden Fall ausgeführt und der Code wird ausgeführt. Ich würde sogar das NotesThread.stermThread() in den finally Bereich verschieben, damit falls eine andere Exception als NotesException innerhalb des try catches Blocks auftritt auch das Beenden der Notesumgebung zuverlässig durchgeführt wird. Dieses minimale Codepattern mit zuverlässigen recycle der Session beim Verlassen des Thread/Programms sollte jedes Standaloneprogramm oder RCP Plugin oder Servlet beinhalten. Alles andere führt über über kurz oder lang zu Instabilitäten. Die einzige Ausnahme zum recyclen von Session gilt für Agents. In Agents wird eine Session vom Agentmanager zur Verfügung gestellt und diese darf man nicht recyclen, da sich um das Recyclen der Agentmanager selber kümmert.

Hier ein Beispiel für einen Agent:

public class JavaAgent extends AgentBase {

    public void NotesMain() {
      Database otherDatabase=null;
      try {
          Session session = getSession();
          AgentContext agentContext = session.getAgentContext();
          Database currentDatbase=session.getCurrentDatabase();          //Diese Datenbank sollte man nicht recyclen.
          otherDatabase=session.getDatabase("", "test.nsf"); //Diese Datenbank sollte man recyclen.
         
       } catch(Exception e) {
          e.printStackTrace();
       }
      finally{
          if(otherDatabase!=null){
            try {
                otherDatabase.recycle();
            } catch (NotesException e) {
                e.printStackTrace();
            }
          }
      }
   }
}


Wir haben in diesem Post einmal gesehen, wie man zuverlässig die Session beim verlassen seines Codes bereinigt und auch ein wenig geschaut wie das ganze bei Agents funktioniert. Wenn man diese Ratschläge beherzigt ist man schon vor einigen Problemen gefeit, da das recyclen der Session sämtliche anderen erzeugte C++ Objekte die von der Session abgeleitet sind auch recycelt Im nächsten Teil meines Erklärungsversuch von recycle kümmern wir uns dann um Objekte die nicht so lange warten können bis die Session recycelt werden.


No comments:

Post a Comment