Autor Thema: Java Agent recyclen  (Gelesen 5298 mal)

Matze84

  • Gast
Java Agent recyclen
« am: 08.07.13 - 10:11:05 »
Guten Morgen zusammen!

Ich hab des öfteren Probleme mit der Speicherbelastung durch einen Java-Agent, der mit LotusNotes-Objekten arbeitet. Ich hab mir diverse Beiträge zum Recyclen von NotesObjekten durchgelesen und meines Erachtens diese auch in meinem Agenten umgesetzt, trotzdem habe ich im Vergleich zu der LotusScript Variante einen eklatant hörenen Speicherverbrauch (der auch nicht mehr freigegeben wird).

Ich verwende im Agenten einen eigenen Datentyp, der String-Objekte und ein Datums-Objekt aufnimmt.

Kann mir jemand sagen, ob ich das Recyclen richtig mache oder wo NotesObjekte die Referenz verlieren und weiterhin im Heap bestehen bleiben?

Gruß

Matthias

Code
mport java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import datentyp.LogEntry;
import lotus.domino.*;

public class JavaAgent extends AgentBase {

	public void NotesMain() {

		try {
			//Globale Variablen
			Session session = getSession();
			Database db = session.getDatabase(session.getServerName(), "domlog.nsf");
			Database dbCurrent = session.getCurrentDatabase();
			View viewEinstellungen = dbCurrent.getView("(Einstellungen)");
			Document docZusammenfassung, docProfilDokument, docTmp, docDomLog;
			LogEntry logEntryTmp = new LogEntry();
			HashMap<String, LogEntry> listLogEntries = new HashMap<String,LogEntry>(100000);
			List<String> listDelete = new ArrayList<String>();
			Name nameUsername = null;
			DateTime dtErstesDoc, dtLogEntry, dtStichtag;
			String strRequest, strTranslatedURI;
			int intStatus, intZaehlerTageStay = 14, intZaehlerTageEdit = 1, intDifferenz;

			//Profildokument auslesen (Anzahl der Tage die stehengelassen werden sollen
			docProfilDokument = viewEinstellungen.getDocumentByKey("ProfileDocument");
			if(docProfilDokument != null)
			{
				intZaehlerTageStay = docProfilDokument.getItemValueInteger("AnzahlTageStayNO");
				intZaehlerTageEdit = docProfilDokument.getItemValueInteger("AnzahlTageEditNO");
			}
			docProfilDokument.recycle();
			viewEinstellungen.recycle();

			dtStichtag = session.createDateTime("01.01.2000");
			dtStichtag.setNow();
			dtStichtag.adjustDay(-intZaehlerTageStay);


			for(int j=0; j<intZaehlerTageEdit; j++)
			{	  
				//Collection aus allen Dokumenten in der domlog.nsf bilden
				DocumentCollection collDoc = db.getAllDocuments();
				docDomLog = collDoc.getFirstDocument();
				if(docDomLog == null)
				{
					break;
				}

				dtErstesDoc = docDomLog.getCreated();
				dtLogEntry = docDomLog.getCreated();
				intDifferenz = dtStichtag.timeDifference(dtErstesDoc)/86400;


				//Solange Dokumente von einem Tag vorhanden sind und diese nicht aufgrund der Eingabe aus dem
				//Profildokument stehengelassen werden sollen
				while(docDomLog != null && dtLogEntry.getDateOnly().equals(dtErstesDoc.getDateOnly()) &&  intDifferenz>= 0)
				{
				                /**
				                   *Hier kommt weiterer Code, der jedoch keine LotusNotes Objekte verwendet
					  */
	
				
					//Erstes Dokument
					if(listLogEntries.size() == 0)
					{
						LogEntry logEntry = new LogEntry(strSearchString, 1, dblContentLength,strUsername, strUseradresse, strZugriff,
								strTranslatedURI, strServer, intStatus, dtLogEntry);
						listLogEntries.put(strSearchString, logEntry);
					}
					//für jedes weitere Dokument
					else
					{
						logEntryTmp = listLogEntries.get(strSearchString);
						if(logEntryTmp != null)
						{
							//Es besteht bereits ein passender Eintrag
							logEntryTmp.setIntAnzahlZugriffe(logEntryTmp.getIntAnzahlZugriffe()+1);
							logEntryTmp.setDblContentLength(logEntryTmp.getDblContentLength()+ dblContentLength);
							listLogEntries.put(strSearchString, logEntryTmp );
						}
						else
						{		  
							//Es besteht noch kein passender Eintrag
							LogEntry newLogEntry = new LogEntry(strSearchString, 1, dblContentLength,strUsername,   strUseradresse, strZugriff,
									strTranslatedURI, strServer, intStatus, dtLogEntry);
							listLogEntries.put(strSearchString,newLogEntry);
						}
					}

					//dtLogEntry.recycle();
					docTmp = collDoc.getNextDocument(docDomLog);
					if(docTmp != null)
					{
						//dtLogEntry.recycle();
						dtLogEntry =docTmp.getCreated();
					}
					listDelete.add(docDomLog.getUniversalID());
					docDomLog.recycle();
					docDomLog = docTmp;



				}//Ende while-Schleife

				//Schreiben der Dokumente
				//######################################################################################################
				for(Object value : listLogEntries.values())
				{
					logEntryTmp = (LogEntry) value;
					docZusammenfassung = dbCurrent.createDocument();
					docZusammenfassung.replaceItemValue("Form", "Datenbankzugriff");
					DateTime datum = session.createDateTime(logEntryTmp.getDatum().getDateOnly());
					docZusammenfassung.replaceItemValue("DatumDT", datum);
					docZusammenfassung.replaceItemValue("UsernameTX", logEntryTmp.getStrUsername());
					docZusammenfassung.replaceItemValue("UseradresseTX", logEntryTmp.getStrUseradresse());
					docZusammenfassung.replaceItemValue("ServerTX", logEntryTmp.getStrServer());
					docZusammenfassung.replaceItemValue("ZugriffTX", logEntryTmp.getStrZugriff());
					docZusammenfassung.replaceItemValue("StatusNO", logEntryTmp.getIntStatus());
					docZusammenfassung.replaceItemValue("TranslatedURITX", logEntryTmp.getStrTranslatedURI());
					docZusammenfassung.replaceItemValue("AnzahlZugriffeNO", logEntryTmp.getIntAnzahlZugriffe());
					docZusammenfassung.replaceItemValue("ContentLengthNO", logEntryTmp.getDblContentLength());
					docZusammenfassung.save(true,true);
					docZusammenfassung.recycle();
					datum.recycle();
					logEntryTmp.datum.recycle();
				}
				//#####################################################################################################
				listLogEntries.clear();
				dtErstesDoc.recycle();
				
				for(int i=0; i<listDelete.size(); i++)
				{
					docTmp = db.getDocumentByUNID(listDelete.get(i));
					docTmp.removePermanently(true);
					docTmp.recycle();
				}

			} //Ende erste for-Schleife


			//Recycle NotesObjekte
			dtStichtag.recycle();
			db.recycle();
			dbCurrent.recycle();
			session.recycle();
		} catch(Exception e) {

			e.printStackTrace();
		}
	}


Offline Ralf_M_Petter

  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 1.879
  • Geschlecht: Männlich
  • Jeder ist seines eigenen Glückes Schmied
    • Ralf's Blog
Re: Java Agent recyclen
« Antwort #1 am: 08.07.13 - 11:47:10 »
Natürlich kann jetzt keiner deinen Code durchanalysieren, aber was auffällt ist dass du session recycelst, was man in einem Agent laut Doku auf keinen Fall machen solltest. Auf der anderen Seite sehe ich kein recycle für collDoc. Wie misst du denn den zusätzlichen Speicherverbrauch.

Grüße

Ralf
Jede Menge Tipps und Tricks zu IT Themen findet Ihr auf meinem Blog  Everything about IT  Eine wahre Schatzkiste sind aber sicher die Beiträge zu meinem Lieblingsthema Tipps und Tricks zu IBM Notes/Domino Schaut doch einfach mal rein.

Matze84

  • Gast
Re: Java Agent recyclen
« Antwort #2 am: 08.07.13 - 11:57:09 »
Danke für die Info!

Die Speicherbelastung lese ich an der Info aus dem DDM ab:

Agent Manager: Very High memory usage by agent 'XXX' in database 'XXX'. Threshold level Very High. Agent Owner: XXX'.

Diese Meldung bekomme ich beim LotusScript Agenten nicht

Offline Ralf_M_Petter

  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 1.879
  • Geschlecht: Männlich
  • Jeder ist seines eigenen Glückes Schmied
    • Ralf's Blog
Re: Java Agent recyclen
« Antwort #3 am: 08.07.13 - 12:02:48 »
Hm vielleicht hat dein Problem überhaupt nichts mit Recycle zu tun. Du verwendest eine HashMap mit 100000 Einträgen. Keine Ahnung wie lange die LogEntry sind, die du da reinstellst. Aber wenn jeder LogEntry  z.b. 1024 bytes hat, dann reden wir hier alleine schon von 100 Megabyte an Daten. Die im Hauptspeicher liegen. Keine Ahnung wie hier die Threshholds sind. Aber ich denke mal, dass könnte dein Problem erklären.

Grüße

Ralf
Jede Menge Tipps und Tricks zu IT Themen findet Ihr auf meinem Blog  Everything about IT  Eine wahre Schatzkiste sind aber sicher die Beiträge zu meinem Lieblingsthema Tipps und Tricks zu IBM Notes/Domino Schaut doch einfach mal rein.

Matze84

  • Gast
Re: Java Agent recyclen
« Antwort #4 am: 08.07.13 - 12:19:18 »
Jedes LogEntry besteht aus 6 Strings,  3 primitiven Datentypen und einem DateTime-Objekt.  Ich denke der Speicherverbrauch dürfte sich in Grenzen halten.

Ich könnte mir vorstellen, dass eine DocumentCollection aus 3-4 Millionen Documenten eher ein Speicherproblem werden könnte

Offline Ralf_M_Petter

  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 1.879
  • Geschlecht: Männlich
  • Jeder ist seines eigenen Glückes Schmied
    • Ralf's Blog
Re: Java Agent recyclen
« Antwort #5 am: 08.07.13 - 12:21:43 »
Ja aber wie lang sind die Strings?

Grüße

Ralf
Jede Menge Tipps und Tricks zu IT Themen findet Ihr auf meinem Blog  Everything about IT  Eine wahre Schatzkiste sind aber sicher die Beiträge zu meinem Lieblingsthema Tipps und Tricks zu IBM Notes/Domino Schaut doch einfach mal rein.

Offline Ralf_M_Petter

  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 1.879
  • Geschlecht: Männlich
  • Jeder ist seines eigenen Glückes Schmied
    • Ralf's Blog
Re: Java Agent recyclen
« Antwort #6 am: 08.07.13 - 12:26:00 »
Vielleicht arbeitest du in dem Code der fehlt auch mit Trim oder substring was deine Probleme leicht erklären würde.

Grüße

Ralf
Jede Menge Tipps und Tricks zu IT Themen findet Ihr auf meinem Blog  Everything about IT  Eine wahre Schatzkiste sind aber sicher die Beiträge zu meinem Lieblingsthema Tipps und Tricks zu IBM Notes/Domino Schaut doch einfach mal rein.

Matze84

  • Gast
Re: Java Agent recyclen
« Antwort #7 am: 08.07.13 - 12:28:28 »
Ja substring verwende ich mehrfach. Meinst du, dass durch die dadurch bedingte Neu-Bildung von Strings wäre der erhöhte Speicherbedarf zu erklären ? (Thema Strings und immutable)

Offline Ralf_M_Petter

  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 1.879
  • Geschlecht: Männlich
  • Jeder ist seines eigenen Glückes Schmied
    • Ralf's Blog
Re: Java Agent recyclen
« Antwort #8 am: 08.07.13 - 12:40:04 »
Nein das Gegenteil ist das Problem. In der Notes JVM belegt ein Substring genau den gleichen Speicherplatz wie der String aus dem er herausgeschnitten wurde.
 
Ein Beispiel:

Du liest eine Datei ein,  in der jeder Datensatz kommaseparierte Werte enthält. Du liest jeden Satz und schneidest mit substring das Feld heraus, das du brauchst und nimmst als Schlüssel einer Hashmap. Dein Wert den du herausschneidest ist kurz deshalb sollte der Hauptspeicherverbrauch gering sein. Das ist er jedoch nicht, da jeder Substring den Hauptspeicher des gesamten Satzes benötigt. Wobei man fairerweise sagen muss, dass sich alle Substrings den selben Hauptspeicherbereich teilen. Das heisst wenn du alle Felder aus dem kommaseparierten String verwenden würdest hättest du keinen Nachteil.

Erklärung ein String besteht im großen und ganzen aus einem char Array. Wenn du nun Funktionen wie z.B. trim oder substring aufrufst, wird zwar ein neues String Objekt erzeugt, aber die Referenz des char Arrays verweist auf das selbe Array wie beim Ursprungsstring. Im String Objekt werden nur 2 ints gespeichert, die angeben, wo der ausgeschnittene String beginnt und wo er wieder endet. Das sollte eine bessere Performance und Hauptspeicherersparnis bringen. Da mittlerweile die Nachteile überwiegen, wurde diese Vorgehendsweise in Java 7.x geändert. Notes verwendet aber noch immer Version 6.x. Deshalb Vorsicht wenn du Daten mit Substring ausschneidest und dann in einer Hashmap speicherst.

Grüße

Ralf
Jede Menge Tipps und Tricks zu IT Themen findet Ihr auf meinem Blog  Everything about IT  Eine wahre Schatzkiste sind aber sicher die Beiträge zu meinem Lieblingsthema Tipps und Tricks zu IBM Notes/Domino Schaut doch einfach mal rein.

Offline eknori

  • @Notes Preisträger
  • Moderatoren
  • Gold Platin u.s.w. member:)
  • *****
  • Beiträge: 11.728
  • Geschlecht: Männlich
Re: Java Agent recyclen
« Antwort #9 am: 08.07.13 - 12:42:16 »
Habe mir den Code mal angesehen und noch ein paar Minuten meiner Pause übrig.

Zunächst einmal

Code
HashMap<String, LogEntry> listLogEntries = new HashMap<String,LogEntry>(100000);

Das kann dynamisch erledigt werden, also die 100000 weglassen. Sollte aber nicht das Problem sein.

Dein recycling ist soweit OK, aber ich würde den Code etwas anders aufbauen. Zunächst mal die Dokumente / Values in eine Collection zu saugen und dann in ein neues Object, das in der Map gespeichert wird ...

Ich würde mir eine view bauen, die die paar daten als Spalten enthält ( spart dir die Berechnung im Code )
Dann mit ViewNavigator über die View laufen und aus jeder Zeile ein neues Dokument schreiben. ( spart dir das "öffnen" des documents, da die Werte direkt aus der View gelesen werden.

Du hast dann auch keine Collection und Hashmap mehr ( spart eindeutig ein paar Byte im Speicher )

Nach dem catch { ... } würde ich noch einen finally block einfügen und das recycle der globalen Objekte ( db, session, viewNavigator, ... ) dorthin verlagern.

Zum Thema ViewNavigator bin ich schon einige Male auf diversen veranstaltungen vertreten gewesen http://www.eknori.de/2012-06-03/xgrid-and-performance-optimization/

( Das JSON brauche ich in meinem Beispiel nur für das Grid, du kommst ohne aus. )



Egal wie tief man die Messlatte für den menschlichen Verstand auch ansetzt: jeden Tag kommt jemand und marschiert erhobenen Hauptes drunter her!

Matze84

  • Gast
Re: Java Agent recyclen
« Antwort #10 am: 09.07.13 - 07:52:51 »
Vielen Dank für die schnelle Hilfe! Ich werde euere Anregungen versuchen umzusetzen und sage dann Bescheid, ob ich damit eine Performance-Verbesserung hinbekomme.

Grüße aus dem Süden

Matze

 

Impressum Atnotes.de  -  Powered by Syslords Solutions  -  Datenschutz