There has been ongoing confusion over memory management in the Java classes. This article is intended to provide background and guidance on how to approach this issue and best practices.
While LotusScript memory management is automatic, enabled by precise messaging from the language to the backend, Java memory management is part of the API in the form of the recycle() method available on every backend class. This is because the language itself handles memory de-allocation within the language itself, but communicates infrequently with the backend. Only when the garbage collector detects an object to be cleaned up do we have the opportunity to release resources for a Java backend object. Because garbage collection is only aware of its own heap, it is not motivated to manage Domino resources, and cleanup there often lags behind.
Objects become eligible for garbage collection when they go out of scope. Having said that, there is no guarantee when they will be collected. Since the garbage collection feature is loosely specified, details of collection vary by platform, JVM, and system load. When garbage collection does occur, it is asynchronous to the logic of the user's application. Since it runs on a lower priority thread, it may interfere with execution of the program as it precipitates lockdown of the backend object tree while the object resources are recovered. Most important to know, there is no guarantee the cleanup will happen fast enough to keep up with consumption of Domino resources. This is especially true for applications which run on the webserver, either as agents, XPages, or servlets. These applications share resources with all other applications running on the webserver, and it is extremely important to maintain the smallest possible resource footprint to ensure server health.
Therefore, it is far more reliable for the application to recover resources by calling recycle() on any object before it goes out of scope. This ensures timely cleanup at a logically efficient time with no contention among backend objects.
|
I. Introduction
There has been ongoing confusion over memory management in the Java classes. This article is intended to provide background and guidance on how to approach this issue and best practices.
While LotusScript memory management is automatic, enabled by precise messaging from the language to the backend, Java memory management is part of the API in the form of the recycle() method available on every backend class. This is because the language itself handles memory de-allocation within the language itself, but communicates infrequently with the backend. Only when the garbage collector detects an object to be cleaned up do we have the opportunity to release resources for a Java backend object. Because garbage collection is only aware of its own heap, it is not motivated to manage Domino resources, and cleanup there often lags behind.
Objects become eligible for garbage collection when they go out of scope. Having said that, there is no guarantee when they will be collected. Since the garbage collection feature is loosely specified, details of collection vary by platform, JVM, and system load. When garbage collection does occur, it is asynchronous to the logic of the user's application. Since it runs on a lower priority thread, it may interfere with execution of the program as it precipitates lockdown of the backend object tree while the object resources are recovered. Most important to know, there is no guarantee the cleanup will happen fast enough to keep up with consumption of Domino resources. This is especially true for applications which run on the webserver, either as agents, XPages, or servlets. These applications share resources with all other applications running on the webserver, and it is extremely important to maintain the smallest possible resource footprint to ensure server health.
Therefore, it is far more reliable for the application to recover resources by calling recycle() on any object before it goes out of scope. This ensures timely cleanup at a logically efficient time with no contention among backend objects.
II. How to recover resources
Garbage Collection is enabled only as a backup strategy to keep the host machine running as long as possible for delinquent applications.
It should never be relied on to do memory management. Memory management should always be done deliberately by the application using the recycle() method.
The recycle() method can be called on any backend object. It is important that recycle be called before an object goes out of scope, otherwise it will become visible to the garbage collector, and there may be inefficient contention between the garbage collection thread and program logic.
The most common situation that starves Domino for resources are collection loops that access lots of Documents or ViewEntries. Below are best practice loops for some of these situations:
Session ses = NotesFactory.createSession();
Database db = ses.getDatabase("mydb");
System.out.println("DocumentCollection Example" );
DocumentCollection dc = db.getAllDocuments();
int idx = 0;
String topicstr;
Document doc = dc.getFirstDocument();
While (doc != null && idx < 5000) {
Item topic = doc.getFirstItem("Topic"); // note: Item is instantiated here for memory management illustration only
if (topic != null)
topicstr = topic.getValueString();
else
topicstr = "";
System.out.println("idx: "+idx+", noteid: "+doc.getNoteID() + " Topic: " + topicstr);
tmp = dc.getNextDocument(doc);
doc.recycle(); // also killls Item
doc = tmp;
idx++;
}
dc.recycle();
View view = db.getView("$All");
ViewNavigator nav = view.createViewNav();
view.setAutoUpdate(false);
System.out.println("ViewNavigator/ViewEntry Example" );
int idx = 0;
ViewEntry ve = nav.getFirst();
while (ve != null && idx < 5000) {
ve.setPreferJavaDates(true);
System.out.println("idx: "+idx+", pos: "+ve.getPosition('.')+", values: "+ve.getColumnValues());
tmp = nav.getNext(ve);
ve.recycle();
ve = tmp;
idx++;
}
System.out.println("ViewEntry example with document");
int idx = 0;
ViewEntry ve = nav.getFirst();
while (ve != null) {
ve.setPreferJavaDates(true);
System.out.println("idx: "+idx+", pos: "+ve.getPosition('.')+", values: "+ve.getColumnValues());
Document doc = ve.getDocument();
System.out.println("idx: " + idx + " NoteID: " + doc.getNoteID());
tmp = nav.getNext(ve);
doc.recycle();
ve.recycle();
ve = tmp;
idx++;
}
db.recycle(); // also kills View & ViewNavigator
ses.recycle(); // kills everything! (db.recycle only necessary if session not ending here...)
III. Recycling containers
An easy way to clean up odds and ends is to recycle container objects. The major containers in Domino are Session, Database, View, and Document. Recycling a container also destroys all its child objects. For example, recycling a Document also cleans up all child Items and RichTextItems. Recycling a Database cleans up all Documents. Recycling a View cleans up all ViewNavigators and ViewEntryCollections. Note that Database is the container for Documents, so recycling a View does not necessarily recover all Documents, but recycling a Database does.
While it is convenient and very efficient to recycle only containers, it may not be sufficient. This strategy only works if a few child objects are in play. For example, agents own the backend Session object. When an agent terminates, the Session is recycled by the agent itself. Anything under the Session is recovered. For non-looping agents running under the Agent Manager, utilizing only a few objects, this is often all that needs to be done. However, if the same agent runs under the webserver with many, many copies of itself executing, it may be a "bad citizen", starving the webserver of resources needed by others.
If many objects are used, individual objects should be recycled, certainly those iterated within loops. One exception is Item objects. The memory footprint of an Item object in the backend is exceptionally light, and it is seldom worth the trouble to recycle them individually. Recycling their parent Document container is the more efficient way, unless hundreds are being addressed.
IV. Special Topics
A. Names & DateTime objects
Common culprits in out of memory situations are Name and DateTime objects. The reason is that they are both children of the Session container; and, left on their own, tend to hang around for the whole duration of the application unless cleanup is done. Take special care to recycle these objects as soon as possible so they don't exhaust the system for resources.
B. ColumnValues
DateTime objects can appear in ColumnValues Vectors, depending on how View columns are defined. If they do, they must be recycled so as not to leak. For 8.52 there are two new method calls to avoid the need for explicit memory management of the DateTime objects. On either ViewEntry or Document, the property setPreferJavaDates may be set to true prior to calling getColumnValues(). In this case Java Date objects will be created instead of DateTime objects. Since Java Dates are managed completely within the Java heap, they do not need to be recycled.
C. Long running applications
A long running server based application which runs continuously deserves special consideration. Here, memory management must be immaculate - all backend objects must be accounted for. Since the application does not terminate as an agent does, where the Session object is owned by the agent and automatically recycled at agent shutdown, it is good to periodically recycle and recreate the Session object. This protects the application from any leaks that have escaped reclamation. A good way to handle server-like applications which service requests is to handle each request on its own Session.
D. Multi-threading considerations in memory management
Since the backend classes share most Domino objects within a Session, it is best to design multi threaded long running applications so that there is no crosstalk of objects among threads. For example, if the same Document is instantiated on different threads under the same backend Session, each Java instance will point to the same backend document object. Since threads can and do run at the same time, it is very hard to know when it is safe to call recycle() on the Document. By far the best way to design the application so the situation does not come up. If data is needed from a Document on multiple simultaneous threads, extract and cache the data. Alternatively, protect critical sections of the threads from each other with Synchronize and instantiate and recycle the document within the critical section.
Domino is a thread sensitive platform. That is, certain objects are sensitive to threads, such as Database, View, DbDirectory, and Directory. Even though Database and View objects can be accessed across threads, the approved guideline is to create and recycle these objects on the same thread. Terminate any thread which accesses these objects prior to recycling them. Note that since garbage collection executes on its own thread, these objects cannot be reclaimed by the Garbage Collector. Recycle must be called on Database and View explicitly, or they will remain alive until the Session is recycled. Note that DbDirectory, and Directory objects should be created and accessed only on one thread.
A special note on performance when terminating threads is in order. Calling NotesThread.sTermThread can be an expensive operation, because historically memory management practice was to close out any thread sensitive objects instantiated on that thread, including Session. To protect the object model, the entire object tree is locked during reclamation. This can impact performance of the webserver if there is a lot of reclamation to do. Therefore it is important that nothing is left to be reclaimed when sTermThread is called. That case is very optimized, and will have no effect on performance. The guideline is, recycle any object created on a thread prior to calling sTermThread on that thread. Remember that since objects are shared within a Session, the first method call that instantiates the object is the creator of the object. For example if calling Session.getDatabase("", "foo.nsf") twice, only one backend database is actually created. If this happens on different threads (not recommended unless synchronized), the thread of the first caller owns the object for memory management purposes. Exception: DbDirectory, and Directory objects are not shared within the backend. Since they must be used within a single thread, a separate instance should be created for any thread where they are utilized.
E. Finalize
One last note on the Finalize clause within the Java language. There is a longstanding problem with Java, JVM, and the JNI language framework which makes a system prone to hangs. Therefore, do not ever do any memory management of backend objects, i.e. call recycle(), or permit an object to go out of scope within a Finalize clause. The resulting intermittent system hangs will occur between the JVM and backend are very hard to diagnose. We see this from time to time with customer applications, and it is far easier to avoid the problem at the outset than to detect and fix |