Memory saving techniques?

[quote=“ikaruga,post:12,topic:34285”]
This is mostly nitpicking, but in this case (removing an element) you could do just


Enemy tmp = enemies.get( enemies.size() - 1 );
enemies.set(i, tmp);
enemies.remove( enemies.size() - 1 );

You can allocate tens of thousands of objects per second and the garbage collector can clean them up very quickly. However doing this can cause quite significant pause times on older JVMs (the majority of JVMs), especially when run on older machines. I once wrote a game which had thousands of stars move across the screen. It would very noticeably pause once a second due to garbage collection and just caching all the dead stars resolved this issue.

I even have my own implementations of HashMap and LinkedList which cache the nodes they create inside, designed specifically for times where you are repeatedly adding and removing lots of elements to a map or list. Again these were produced in response to noticeable pause times when my games were run on older JVMs.

Even if your not allocating thousands of objects per frame, when it does eventually collect there will probably be a noticeable pause.

But I’d always advise caching objects in response to GC issues, not in advance.

You can work around this issue by tweaking the GC parameters. To ruin it, some moron at Sun decided these JVM args were unsafe for applets and webstarted apps, so we’re stuck with writing these object-pools.

That’s a really good idea to move the object you’re removing to the end of the ArrayList before you get rid of it - I’ve never thought to do that before, actually. And as for the ConcurrentModificationException, I have to say I’ve never understood exactly why I would sometimes get it with Iterators, but let me clarify.

I think I was doing something like this:


for (Iterator i = list.iterator(); i.hasNext();)
{
    Object o = i.next();
    if (o.isMarkedForDeletion())
    {
        list.remove(o);
    }
}

The above will give you a ConcurrentModificationException, but aside from that is silly to do because you’re not even bothering to use the Iterator you’ve already created. I’m guessing the below won’t give you an Exception:


for (Iterator i = list.iterator(); i.hasNext();)
{
    Object o = i.next();
    if (o.isMarkedForDeletion())
    {
       i.remove();
    }
}

And that’s obviously the way you should be doing it - but I really was referring to the fact that when I’m coding these things very quickly I’ll sometimes forget to use the Iterator the whole time and then will get Exceptions. And when I’ve done a lot of code at once then a ConcurrentModificationException starts to look very weird, given that the remove loop is usually a pretty tiny part of things. So I’ll spend a little while wondering where I’m messing with Threads incorrectly (where I usually see those type of Exceptions), finally coming back to the “tiny” mistake in the remove loop. All around, that makes ArrayList just simpler to me - it seems a lot more obvious when you go out of bounds, which is really the only mistake you can make with an ArrayList.

The reason those ConcurrentModificationExceptions are thrown:

  1. The Java Collections API is written to be fail fast, this means:
    if anything happens that shouldn’t be happening, try to crash ASAP

  2. The Iterator is only reliable if the collection it iterates over is not modified.
    Every modification inc()s the private modcount field in the collection
    The modcount is saved when the Iterator is created, and upon every
    access, the current modcount is compared, and if not equal, it fails

  3. It doesn’t matter whether the ‘concurrent’ modification is done on the same
    thread that created the Iterator - it’s still unsafe. When you call it.next(),
    a call to it.remove() MUST remove the current (targetted) object. If you call
    collection.remove(obj), the current (targetted) object might be gone, so the
    next invocation of a Iterator method will fail so that it can guarantee that
    if it does not throw an Exception, it removes the CORRECT object.

It’s a sensible policy, but i have already fallen afoul of it before.
I don’t remember if i was just using non-modification query methods (get), but it’s likely that i was (thus the surprise).

Anyway there are some gotchas with memory still in java. 2 I have experienced, one that from my understanding shouldn’t happen and one that is a “classic”.

  1. I once was working with a library that allocated a huge byte array. The normal jvm (default arguments, client) had no problems with one call of the method, but two threw a exception.
    Slightly weird but, i think this is what was happening:
    byte [] reference;

    inside method
    reference = new byte[huge];

And the second time that huge was called, either the gc couldn’t handle
reference = new byte[huge]; (because it didn’t collect the old array before attribution),
or more likely, the reference escaped into some other object (never did say the code was good, it is a pure java port of unrar), so there were two arrays at the same time. Javolution pooling “solved” it with minimal pain since the array was always of the same size. Even if references escaped they were pointing to the same object by pooling, and would eventually have been set anyway.

  1. Classic. Event driven programming requires callbacks to held into lists to “push” notifications from a component. These callbacks can hold arbitary references to large objects, leading to the classic pattern of event programming of the cleanup(Listener) method. You can avoid this with judicious use of reflection (because sun braindeadness didn’t saw fit to unify the event notifiers into a interface) and a java special type of reference object called WeakReference. Weak references allow the gc to clean up the weak reference when all the hard (normal) references are garbage collected, so no leaks can happen (or notifications) after the listening object disappears.
    Lead me to a few classes like this:

package util.swing;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.logging.Logger;

/**
 * Never do stupid finalizes on PropertyChangeListeners again
 * A delegate for normal listeners that removes them
 * (and their references) automatically when the Listener
 * is no longer registered in any component.
 * @author i30817
 */
public class WeakPropertyChangeListener implements PropertyChangeListener {

    private static Logger logger = Logger.getLogger("util.swing");
    WeakReference<PropertyChangeListener> listenerRef;

    public WeakPropertyChangeListener(PropertyChangeListener listener) {
        listenerRef = new WeakReference<PropertyChangeListener>(listener);
    }

    public void propertyChange(PropertyChangeEvent evt) {
        PropertyChangeListener listener = listenerRef.get();
        if (listener == null) {
            removeListener(evt.getSource());
        } else {
            listener.propertyChange(evt);
        }
    }

    private void removeListener(Object src) {
        try {
            logger.finer("Removing unused listener " + this);
            Method method = src.getClass().getMethod("removePropertyChangeListener", new Class[]{PropertyChangeListener.class});
            method.invoke(src, new Object[]{this});
        } catch (Exception e) {
            logger.severe("Cannot remove listener: " + e);
        }
    }
}

For programming swing, that has a asynchronous event driven programming model.

Obviously, of course, the constructor PropertyChangeListener must be held by a hard reference outside - preferably in the the object that is registering the reference into the notifier. Then when that object goes away, the hard reference also goes away, and any registration of it also goes away.

If you don’t do this, ofcouse, then adding a weaklistener does exactly nothing, since it is immediately (more or less) disposed.

I never use Iterators directly, but I almost always use the enhanced for loop. To avoid ConcurrentModificationExceptions I usually use CopyOnWriteArrayList or iterate over a copy of the list.

Wow, again, thanx. I’m learning so much in one post.

@mh114@ - thanks for the tip. I picked up on that from @pjt33@'s reply.

So just to confirm, in @pjt33@'s code, when the current slot in the ArrayList for the current enemy object is overridden by the last, the current enemy gets taken care of by the GC, right? (In other words, there’s no need to assign the current enemy to the last slot and explicitly remove it…)


if (e.isMarkedForDeletion())
        {
            int n = enemies.size() - 1;
            enemies.set(i, enemies.get(n));
            enemies.remove(n);
        }

Correct (assuming the list is the only place you store references to the enemies).