Tuesday, June 24, 2008
Memory Leaks In LWUIT And Tracking Memory In Java ME
Tracking memory usage in Java ME is a pain and requires lots of understanding among devices and behaviors, the first instinct of many developers is to start littering the code with System.gc() which I wrote about in the past. I'll try to focus here on two distinct aspects: tracking memory in Java ME and memory pitfalls within LWUIT.
Lets start with tracking and observing memory usage, first don't rely on Runtime.free/totalMemory()... These methods lie. Don't rely on numbers from the WTK memory monitor, they too aren't correct. I found the Nokia S40 emulator to be the most accurate in terms of memory usage (although a bit of a pain to use). I still use the WTK memory monitor but not to track memory. I use it to track live instances of objects...
When I notice that a given application takes too much memory I look over the table of objects in the WTK memory monitor and look at my applications class list, within this list I look at the number of instances for every class. If a class has too many instances this is the point to investigate using a debugger.
E.g. recently memory was becoming an issue in an application after entering a given form, using the WTK memory monitor I noticed that with every visit to the form a new instance of the form was created (which is the right behavior) but the old instance wasn't cleared from memory.
What was keeping this form in memory?
LWUIT components are in a tree form similar to AWT/Swing and many other component toolkits. In LWUIT every component has a pointer to its parent accessible through the getParent() method. So essentially if a single component within the form is kept in memory the entire form will remain.
So I started with the obvious, I reviewed the form creation code looking all over to someone passing "this" pointers around. Then I looked for components being stored as class members (sure sign of a memory leak), no luck there either. This was getting harder than a typical session...
The solution to tricky problems like this is elimination, remove features until the problem is resolved. This can be misleading in memory leaks since leaked memory can be reduced if you remove images but the problem might not be solved... So the solution was to check against the WTK memory monitor which includes the instance count rather than memory, this displays the actual leak regardless of its size.
To make a long story short: it was the model coupled with a problematic LWUIT design decision.
The problematic form kept its ListModel instance throughout the application, which shouldn't be a problem. However, the List registers itself as a listener to the model so it can update based on model changes. Since the model was kept so was every list displaying the model and every parent form of every list!!!
Ugh.
We worked around this by adding a "flushListeners" method to that specific model implementation which removed all listeners.
Newer versions of LWUIT will be smarter about binding to list models, when initComponent() is invoked the List in the new LWUIT drop would bind the listener to the model. When deinitialize() is invoked the list removes itself as a listener. This will implicitly allow the list to be garbage collected while the user can keep holding the model which is a sensible use case.
We probably don't use initComponent() and deinitialize() enough across LWUIT, we initially wanted to avoid such complexities but due to all the issues of constructor order and initialization complexity we had to add them. They are invoked before showing the form (or adding to a visible form) and after removing the form implicitly. These methods are very powerful tools in the arsenal of MIDP developers.
So here are some rules of thumb for debugging a memory leak in ME:
1. Identify the problem in object terms not in heap terms - try to find out the class/object that is leaking, not the amount of memory that is leaking.
2. Narrow down the problem - remove features until you find the exact features responsible.
3. Some memory leaks are only felt on an emulator (e.g. Nokia S40 emulator) but can still be tracked using the WTK memory monitor which is a simulator. This doesn't imply a bug in one or the other they are just very different in their implementation.
4. Increase the amount of memory used by some elements to accentuate the problem - add dummy arrays as members of classes so the problems will be more visible.
5. Emulators don't support triggering GC, just navigate a bit in the UI to cause GC. The amount of memory taken is meaningless if there was no GC beforehand.
6. If triggering GC manually helps then check your code. This generally helps in cases where a connection wasn't closed or a resource was not released. Some phones might not be as forgiving for these errors and GC calls just hide the problems.
7. Don't keep members for classes if possible, pass data on the stack. This is easier in threading terms and solves lots of small memory gotchas, the cost of passing on the stack is very low in todays phones. Threading mistakes are just impossible to debug on any device...
8. Speed and memory can be traded off and this can be decided on a per device basis. E.g. LWUIT's Indexed Image. Furthermore, you can store state in RMS and access it dynamically on a low memory device while keeping it accessible for a high end device.
9. Runtime.freeMemory/totalMemory has nothing to do with reality in modern devices don't treat it as a fact.
10. Device limitations are often accessible via the API, read the documentation properly e.g. JSR 184 has properties that define everything such as the maximum texture size thats supported on the device.
For LWUIT specific tips on memory and performance check our developers guide.
Subscribe to:
Post Comments (Atom)
Hi' Ive been developed J2ME Application on CLDC 1.1, MIDP 2.0. Now i'm looking for the device list, which are supports to My Apps.
ReplyDeleteHi there,need some help.i am new to lwuit and am facing memory problems on low end phones.You mentioned that u added flushlisteners to your model.Would you mind sharing how you did that. Been trying to achieve the same albeit with no success
ReplyDelete