I have an app that downloads a csv file online then saves it locally so the app will work even if it is offline. My problem is when the user closes the app then opens it again immediately, the app hangs while parsing the saved csv file and throws OutOfMemoryError. However, I noticed that when I open the app again after a few minutes it works just fine. The downloading, parsing and saving are done on separate threads. What can be the solution to this?
1 Answer
One possibility: out-of-memory errors can have more to do with an overworked GC than with an actual shortage of memory. If you allocate large pieces of memory, then free them, then allocate even larger pieces, you get to a point where you have a lot of large bits of free memory taking up space but unusable because they're not large enough. The GC is frantically trying to move things around and merge these pieces into one contiguous block for the next allocation, but rather than look bad because it's taking too long, it will just throw an OutOfMemory exception, even though 90% of memory is theoretically available (and will be available if you can give it a minute).
In your case, I'd suspect ArrayList. It keeps an array of references. As you add entries, it adds to the array. When it runs off the end, it allocates a new, bigger, one and frees the old one. These discards pile up if you keep it busy. Hashtables have similar problems. LinkedList and TreeMap don't, because they work with small bits of memory.
I don't know too much about Android, but I'm guessing the app doesn't really close when you close it briefly, so when you restart it it's the same free-memory-fragmented execution as before. If you wait a while it may be a new execution. Even if it's not, the GC has had time to clean things up and you're fine.
The solution you want is probably to force a garbage collection (System.gc()) each time you "start up" your system. It gives the GC a chance to put everything in order before allocating space for you, and it won't take long. In a sense, you're giving the GC permission to lock up your program for half a second, which it would not do on its own. (And if it did, it would pick an awkward time to do it--while the user's entering text, say.)
Avoiding large arrays by using linked collections is another solution, but arrays are fast and when you can spare a half-second of the user's time there's no reason to switch.
Hope this helps. If it's not the problem this time, maybe it will be next time.
Addition: Unfortunately, System.gc() is just a "suggestion". It may not be doing the job we hoped it would do. Or you may be getting into trouble after the call. The other big fix I should have mentioned before would be to set the initial size on ArrayList very large, if that is what your are using. Making it two or three times the size it needs to be will probably save you ten times that amount of memory over a run--and save time, too. This works for any array-based structure (hash tables and plain arrays). Beyond that, pointer-based structures like LinkedList will not have this problem if you can get around their disadvantages.