5
\$\begingroup\$

I have a Java game development framework. Certain classes, such as Texture, need to be disposed as soon as you don't need them - we cannot depend on the Garbage Collector.

For this, I have decided to enforce reference counting like Objective-C. You call retain() when you want to own something, and release() when not anymore. This increases/decreases a counter. When the counter hits 0, dispose() is called - at which point, Texture will gladly free its resources such as pixel data etc.

NOTE

The reason I can't depend on the Garbage Collector, is because things like the pixel data aren't stored in Java, but rather by OpenGL in some bindings. Similar things happen with audio data. So it is necessary to call OpenGL to free that data manually.


On my first attempt, I made an interface:

public interface Retainable {
    void retain();
    void release();
    void dispose();
}

So a class that wants to be part of this MRC scheme would be like

class Texture implements Retainable {
    int retainCount = 1;    // 1 because I want it to begin retained
    public void retain() {
        retainCount += 1;
    }
    public void release() {
        if (--retainCount == 0) {
            dispose();
        }
    }
    public void dispose() {
        // Free pixel data
    }
}

This works and is OK.

However, every class that wants to do be part of the MRC scheme would need to:

  • Create an int field
  • Implement retain()
  • Implement release()
  • Implement dispose()

From the four points, the only non-redundant point is the last one. Obviously dispose() will differ from one class to another. But all the other points won't differ from class to class - I am repeating code, and I don't like that.


On an attempt to improve the simplicity of this scheme, I decided to make a manager class to keep track of all Retainable objects. The class will have a map, where the keys are the Retainable objects and the values are their integer counts. The class will have retain(obj) and release(obj), which increase/decrease the count in the map. When the count hits 0 it will call dispose() and then remove the object from the map.

public abstract class Manager {
    private static HashMap<Object, Integer> data = new HashMap<Object, Integer>();
    public static void retain(Retainable object) {
        if (data.containsKey(object)) {
            data.put(object, data.get(object) + 1);
        }else{
            data.put(object, 1);
        }
    }
    public static void release(Retainable object) {
        if (data.containsKey(object)) {
            int count = data.get(object);
            if (count == 1) {
                data.remove(object);
                object.dispose();
            }else{
                data.put(object, count - 1);
            }
        }
    }
}

While I understand that "global" stuff isn't likeable, I decided to sacrifice that for the sake of simplifying this scheme as much as possible.

I really like this approach, because now my classes don't need to implement so much redundant stuff. They only need to implement dispose().


There is one thing that still bugs me

I would like that, when you construct a Retainable object, it will automatically have a retain count of 1 (so you don't need to call retain() every time you make a new object).

Then, this means that a Retainable class needs to call

Manager.retain(this);

In their constructor... for all classes. Ahh, if only there was a way to construct objects already retained without having to add this line in their constructor (I know this is a super small and almost meaningless thing, but heck...).


Do you have any ideas to improve this? And, very hopefully, a solution to the annoyance above?

\$\endgroup\$
  • \$\begingroup\$ There's two potential forces at play here, and I'm wondering which (or both) you are going after. In particular, there's deterministic release of resources ("Ok, I'm done with this, free it right now."), and there's also additional work to be done when cleaning up resources ("Whenever this texture is cleaned up by the Java GC, tell OpenGL it can release it at a C level as well.") Which of these is the end goal? I'm guessing both, but I would like to make sure. \$\endgroup\$ – Corbin Jun 5 '14 at 23:25
  • 1
    \$\begingroup\$ I recommend you look at the NettyIO interface ReferenceCounted (and its implementations). It is finely-tuned, battle-tested, and scales well enough for Twitter. (The two lead authors work for Apple and Twitter.) \$\endgroup\$ – kevinarpe Dec 10 '15 at 10:04
  • \$\begingroup\$ I expanded on the "look at Netty" answer here: stackoverflow.com/a/52885515/1151521 \$\endgroup\$ – Aleksandr Dubinsky Oct 19 '18 at 3:29
4
\$\begingroup\$

You should not be manually counting references. Instead, you should be letting the garbage collector do the reference management for you, and then hook in to the garbage collection framework when the reference is cleared in Java.

The right tool for the job is the finalize() method. This often-misused method, is actually purpose-designed for this exact case:

the usual purpose of finalize, however, is to perform cleanup actions before the object is irrevocably discarded. For example, the finalize method for an object that represents an input/output connection might perform explicit I/O transactions to break the connection before the object is permanently discarded.

public class Texture {
    ....
    @Override
    protected void finalize() {
        // clear resources kept in external areas to Java....
        .....
    }
}

Note:

The Following 'Old' answer is based on the first version of the question that made no reference to non-Java memory....

I believe you have a basic misunderstanding of Java memory management. What you say you are doing, you are not actually doing, and this statement 'This works and is OK' is not what you think is happening.

You cannot possibly tell java to release the memory for an object. What you can do though, is remove all references to an object, and, at some point Java GC will scan through and identify that memory as no longer used, and available for reuse.

In your class, you have the 'pixel data', perhaps an array of private byte[] pixels = ..., and in your dispose() method you call pixels = null, which removes the reference to the byte[] array. When the Garbage collector sweeps the heap, it will identify that the byte[] array is no longer referenced, and it will release that space.

This is what you are seeing, and you think 'it works'. But, that is not the solution you should be using.

What you should be doing, is not referencing the texture when it is no longer needed!

In other words, there's a bigger picture here... there is somewhere in you code where you have done:

Texture mytexture = new Texture(pixeldata);

and then, you have a reference to mytexture that you are not releasing.

If you say mytexture = null, then the garbage collector will release the pixel data as well as the texture object.

Instead of finding all the places in your code that use the texture, and calling retain() and release() they should just set their Texture variable to null, and move on if they no longer need the Texture.

If you are not releasing your pixels after doing that, it means you have some place in your code where you are still holding references to the Texture, perhaps some value in a HashMap, an array, or somewhere else where you are holding a reference. Remove those entries, and you will be fine.

Note: it is possible that your program is part of a very, very, tiny, small proportion of programs that may need finer memory control, and need to keep a Texture cached even though no-one is using it at a particular moment in time. Java has Soft-References that may help, but that is not likely to be your solution, and is more complicated than this post warrants....

\$\endgroup\$
  • \$\begingroup\$ The pixel data is loaded up by OpenGL bindings. I don't have an array of bytes anywhere in my Java code. It's the same thing libGDX does. So when a Texture is disposed, it is telling OpenGL to free those pixels (I'm not sure how OpenGL works exactly, but I suspect it is not subject to Java's Garbage Collection). \$\endgroup\$ – Omega Jun 5 '14 at 23:05
  • 2
    \$\begingroup\$ Mentioning that you are not managing Java memory would have been helpful. \$\endgroup\$ – rolfl Jun 5 '14 at 23:06
  • \$\begingroup\$ I'm sorry. I will ammend the question. \$\endgroup\$ – Omega Jun 5 '14 at 23:07
  • \$\begingroup\$ I have updated my answer @Omega \$\endgroup\$ – rolfl Jun 5 '14 at 23:15
  • 1
    \$\begingroup\$ The finalize() method gives no guarantees about how soon it will be called after the Object is unreachable. Consequently, you could easily run out of OpenGL memory before the GC gets around to calling your finalize() method. You may be better off managing resources manually in your app, and instead adding warning messaging to the finalize() method to log a message any time finalize() is called when the app hasn't called release(), to help in finding resource leaks. \$\endgroup\$ – jsears Apr 11 '18 at 12:45
2
\$\begingroup\$

The idiomatic mechanism for managing resources in Java is by using an AutoClosable implementation.

The use case would be, in Ugly Java:

Texture rocks = new Texture(.....);
    ... do something.
rocks.close();

In pretty java, it would simply be:

try (Texture rocks = new Texture(...)) {
   ... do something
}

but, you want to share a texture, so the idiomatic approach is a 'pool', with a counter of the pool usage, and a pool shutdown when the pool is empty....

i would go further, and abstract the pool from the off-heap management. Let the pool deal with the reference counting, and let the off-heap management deal with the JNI crap.

Consider a three-level tiered approach (note the generic methods...):

interface OffHeapData<T> extends AutoClosable {
    public T getData();
    // void close() from AutoClosable
}

Then, have an implementation for you OpenGLTexture....

class OpenGLTexture implements OffHeapData<byte[]> {
    private openGLRef datakey;

    // easy way to manage close control
    private AtomicBoolean open = new AtomicBoolean(true);

    OpenGLTexture(....) {
        data = ....; // OpenGL Calls

    }

    public byte[] getData() {
        // retrieve bytes from OpenGL
        // perhaps check open first... but not needed?
        // (won't be completely thread safe)
        // if (!open.get()) {
        //     throw new IllegalStateException(....);
        // }
        return .....
    }

    public void close() {
        if (open.compareAndSet(true, false)) {
            // OpenGL calls to close resource.....
            // only the first call to close() will actually call OpenGL.
            ....
        }
    }

    protected void finalize() {
        // in case there is not a clean close() called.
        // close will do nothing if called twice, so safe to do.
        close();
    }

}

OK, so the above class is a 'decent' off-heap resource manager.

Now, I recommend a reference-counting Factory for it. This is not the same interface as the OffHeapData one.

public interface Texture extends AutoClosable {

    public byte[] getPixels();

}

and a factory that manages these Textures. I would make this a generic class that can be customized for Texture, or Audio data, etc.:

public TextureFactory {

    // simple container that can be locked against.....
    // note the methods are synchronized
    private static final class ManagedTexture {
        private final String key;
        private OpenGLTexture texture = null;
        private int refcount = 0;

        ManagedTexture(String key) {
            this.key = key;
        }

        synchronized OpenGLTexture getTexture() {
            if (refcount++ == 0) {
                // build a new one.... use the key to identify it... whatever
                texture = new OpenGLTexture(key);
            }
            return texture;
        }

        synchronized void returnTexture() {
            if (refcount <= 0) { 
                throw new IllegalStateException(....);
            }
            if (--refcount == 0) {
                texture.close();
                texture = null;
            }
        }
    }

    private static final class TextureImpl implements Texture {
        private final ManagedTexture manager;
        private OpenGLTexture texture;
        private TextureImpl(ManagedTexture manager) {
            this.manager = manager;
            texture = manager.getTexture();
        }

        public byte[] getPixels() {
            return texture.getData();
        }

        public void close() {
            texture = null;
            manager.returnTexture();
        }
    }

    private final ConcurrentMap<String, ManagedTexture> textureMap = new ConcurrentHashMap<>();

    public final Texture openTexture(String key) {
        ManagedTexture mt = textureMap.get(key);
        if (mt == null) {
            mt = new ManagedTexture(key);
            ManagedTexture race = textureMap.putIfAbsent(key, mt);
            if (race != null) {
                // we lost on a race condition, that's OK.
                mt = race;
            }
        }
        return new TextureImpl(mt);
    }
}

Then, in your regular code, you use the standard close-with-resources mechanism for your textures:

private static final TextureFactory factory = new TextureFactory(...);

......

try (Texture rocks = factory.openTexture("path/to/rocks")) {
    .....
    byte[] data = rocks.getPixels();
    .....
}

In essence, you now have a Texture pool, with a clear, and always-safe open/close pairing, and reference counting.

Hope that helps.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.