Recently during the development of J2ME app Arcadia at Playscape Games, we have bumped up against the memory limits of our test handsets, which have heap sizes starting at 800Kb. I don’t believe in premature optimization, and so previously I had done little to measure & conserve memory beyond following a simple but crucial heuristic:
Ensure the objects that are most numerous in your app use memory most efficiently
I’ve now spent some time learning more about Arcadia and general J2ME memory consumption.
Findings from the WTK Memory Profiler
- The Memory Profiler provided in Sun’s Wireless Toolkit 2.5 (WTK) is invaluable! It shows instance counts, instance consumption, and also allocation code sites on the right-hand side (a nice surprise).
- Primitive types boolean, byte and short use 4 bytes in the VM (same as an int), even when grouped together, making them useless from a heap conservation standpoint.
- Some objects sizes in bytes (ignoring contained objects), as reported by the Emulator (but very likely similar in most JVMs)
- Object 12
- String 24
- Integer, Boolean 16
- Vector, Hashtable 24 (plus internal arrays ?16+ contents?)
Fragmented & Unreclaimable Memory
Memory fragmentation or unreclaimable memory (for Image objects on Samsung handsets) has previously been reported on KVM-Users list and J2ME.org, whereby some J2ME app cannot effectively recover freed memory.
I havent yet found any evidence of this on our test handsets (SE K500, Nokia 6233, Motorola V3, Sansung D500), but its hard to tell definitively.
According to this paper, Suns KVM (presumably the basis/inspiration for many handset VM impls) provides a compacting Mark-Sweep garbage collector.
Load & Save
- There is a transient spike in memory usage while levels are loading and saving. I suspect this will tend to be a recurrent architecural issue.
- Data must be loaded from a MIDP RecordStore as byte, which is then parsed and allocated into objects. During this process, the level data is effectively duplicated twice in memory, and only after the level is fully loaded is the byte array discarded. A potential solution is an improved DiscardingByteArrayInputStream, which discards its contents once they have been read(). It would need multiple chunked byte buckets internally to do this.
- Locations in the world map are the most numerous objects and the biggest consumer of memory. I added a lazy-initialisation mechanism so that empty locations dont eagerly allocate space for potential occupants. Big win.
- Unloading unused sprites has also helped. I introduced a lazy-load mechanism there as well.
- Condensing multiple boolean and byte fields into ints
- Reducing the number of audio Player objects in the game by discarding low value sound effects.