We've covered half of the EJAPP Top 10 now. Now let's start with the second half!
Excessive memory usage manifests itself in two ways:
- a large number of allocations (for short-lived objects), or
- a large number of objects per se.
For example, a while ago I encountered an application running on WebSphere 4.0 with IBM JDK 1.3 on AIX. The VM held about ±1.400.000 objects:
- ±200.000 for application server
- ±200.000 for the application
- ±1.000.000 for an i18n module
On top of that a large number of objects were allocated (and subsequently unreferenced) to generate and parse XML messages (something discussed earlier in the EJAPP Top 10 countdown at #7). The problem lay in the fact that the IBM JDK 1.3 garbage collector has just one big heap containing all objects. Every time the garbage collector ran it had to iterate through all those objects, even though most of them were still referenced. This problem was fixed by reducing the static memory usage of the i18n module (it held a large number of identical Strings, so the solution lay in interning them). The picture below shows the CPU usage of the application before and after the fix was installed:
Newer (Sun) VM's will probably not suffer that badly because of their generational garbage collectors. The ±1.000.000 static objects would have ended up in the tenured generation, while the many objects allocated for the XML parsing and generation would have ended up in the young generation. Most of the garbage collection runs would have been minor (and therefore quick) collections that do not need to take the ±1.000.000 objects into account, given that the young generation was large enough. Then again, the fix, which involved interning a lot of Strings might have caused such VM's to run out of space for the permanent generation!
This point demonstrates nicely that, apart from reducing the amount of objects allocated by your application (either short-lived or long-lived), there is a lot to be gained by learning about and tuning the garbage collector to your application. Ideally, this should be part of the application server and JVM tuning we discussed at #9.
Finally, when testing and tuning your application, use a memory debugger (next to the profiler you're already using, right?) to review the contents of the heaps and determine the number of objects allocated and left unreferenced while executing your use cases. A tool like JProbe Memory can provide you with a lot of insight into the memory behaviour of your application. Check the tool report from the Java Performance Tuning web site for an example of what this tool can do.
Performance is not just about how fast your code can run but also about how much memory it uses!