freeing memory allocated by direct buffer

Started by asch, November 17, 2009, 00:51:56

Previous topic - Next topic

asch

Hi!

I am developing a prototype of an embedded GUI using lwjgl. The program performs fine  :). I have good FPS with low CPU usage as desired. The only problem is a memory leak on the long run. The application code was not yet reviewed to double check all resources are freed but a first glance at the code made me suspecting direct buffers (ByteBuffer, IntBuffer, FloatBuffer etc...) are leaking.

I am wondering how direct buffers (ByteBuffer.directAlloc()) are freed. I thought that they must have a dispose() method as the allocated memory is not managed by the garbage collector but by host system's malloc-free. Though the Java API of buffers have no dispose method.

I have googled for the problem of freeing direct buffer area and found nothing that explains the mechanism but someone who does not understand the problem just like me:
http://stackoverflow.com/questions/1744533/jna-bytebuffer-not-getting-freed-and-causing-c-heap-to-run-out-of-memory

My questions:

  • Can freeing of direct buffers really not be controlled by the Java application?
  • Is it possible to use non-direct buffers with the API (all examples I have read all use the direct buffers)?
  • Does lwjgl allocate direct buffers internally?
  • Do you have a recommended method to allocate buffers that can be freed a predictable way? I am thinking about using NewDirectByteBuffer JNI method so I can manage memory allocation and free them myself.

broumbroum

Garbage collecting works if no reference to the buffer is in run. A way to ensure that your buffer becomes empty (direct or undirect) is clearing the backing-array : http://www.java2s.com/Code/JavaAPI/java.nio/ByteBufferclear.htm

asch

Just to reply myself:

In the implementation of java.nio.DirectByteBuffer (Direct-X-Buffer.java 1.50 05/11/17)
the constructor of a direct byte buffer creates a "cleaner":

cleaner = Cleaner.create(this, new Deallocator(base, cap));

this cleaner is registered with the garbage collection mechanism and is guaranteed to run after the object is marked to be garbage collected. The only problem is that garbage collection is started when Java Heap or direct heap usage is high and not before.

The implementation of Bits.reserveMemory(long size) (java.nio.Bits - Bits.java 1.20 06/01/16) has some ugly tweaks. In case there is not enough direct memory to be allocated it calls System.gc() then waits(!) using Thread.sleep(100) before checking the amount of available direct memory again. That is some not predictable wait at random time. It is also not guaranteed that the "clean" feature finishes by this time and frees enough direct heap memory.

You can decide yourself, but I am going to use buffers on Java Heap (non-direct buffers) for not performance critical GL calls. And allocate and deallocate buffers for performance critical calls with JNI - malloc, NewDirectByteBuffer and free. This way managing memory buffer resources is not done by JVM automatically but at least it can be done in a predictable way.

broumbroum

I'd rather choose a SoftReference to handle deallocation, this is the faster way of Java implementation. :)
it needs a ReferenceQueue which will be polled for unlinked Buffer's from SoftReference references.
public class TestSoftRefBuffers {
static ReferenceQueue refQue = new ReferenceQueue();

/** whenever myBuf becomes softly-reachable (as soon as it is not referenced by another 
variable than the softreference) it is enqueued in referenceQueue */
static void freeBuffers(ReferenceQueue q) {
   Reference r;
    while((r = q.poll()) instanceof SoftReference) {
            Buffer b = (Buffer)r.get();
               int c = b.capacity();
             b.clear();
            System.out.println("a buffer of " + c + " bytes has been cleared from heap");
                 r.clear();
   }
}

static Buffer loadStuf() {
  Buffer myBuf = ByteBuffer.allocate(1000);
  for(int i  = 0; i<1000; i++)
     myBuf.put(i);
  myBuf.rewind();
  SoftReference soft = new SoftReference(myBuf, refQue);
  return myBuf;
}

public static boolean run = true;
public static void main(String[] args) throw InterruptedException{    
   while(run){
      Buffer buf = loadStuf();      
      Thread.sleep(300);
      freeBuffers(refQue);
   }
}
}


:D

Evil-Devil

Could this Softreference stuff run within its own thread or does it have to be done in the gameloop?

broumbroum


Evil-Devil

Ah cool. THen i only have to find out when to use regular Buffers and when Native Buffers. Thx broumbroum

broumbroum

well 2.3 specs of lwjgl definitely chose to ban undirect buffers .... I'm really wondering if direct buffers are faster ....

yet lwjgl uses JNI to load up C openGL codes and it may be obvious that direct access can improve perfs. But as read in the Java Docs of ByteBuffer, the java nio "undirect" buffers are smaller on heap and faster to dealloc.... so I'm ^^ really wondering here.... :)

VeAr

Quote from: broumbroum on February 26, 2010, 21:19:15
well 2.3 specs of lwjgl definitely chose to ban undirect buffers .... I'm really wondering if direct buffers are faster ....

yet lwjgl uses JNI to load up C openGL codes and it may be obvious that direct access can improve perfs. But as read in the Java Docs of ByteBuffer, the java nio "undirect" buffers are smaller on heap and faster to dealloc.... so I'm ^^ really wondering here.... :)

You must use direct buffers with OpenGL. If you give an array-backed-buffer (non direct) to LWJLG, it will wrap it in a direct buffer before calling OpenGL.

The only thing that can be done to reduce fragmentation, is to manage your resourced cleverly. Reuse buffers if possible, and use OpenGL mapped buffers. Mapped buffers are managed by OpenGL, so Java doesn't have to GC the buffer.

spasi

What you can do is create your own memory allocator. Initialize a relatively big direct ByteBuffer, then use your "malloc" to get chunks of memory out of it, with ByteBuffer.slice(). Every time you "malloc", you mark that slice of memory as allocated, using a "release" will unmark it, making it available for subsequent allocations. So, you can do as many "lightweight" allocations as you want, without worrying about garbage-collection or anything.

There are many open-source implementations you can find for a decent memory allocator (which is honestly not the simplest thing to create). I use a very simple binary tree implementation, has worked fine for years but it might not be enough for more complex usage cases. I create 2 allocators, one with a small leaf size, one with big leaf size, then the "malloc" service uses one or the other, depending on the amount of memory requested.

Hope this helps. Btw, I think there's also a VM argument you can specify to limit the amount of direct memory iirc (which would probably force direct ByteBuffer memory to be released quicker).