24

JDK provides abillity to allocate so-called direct ByteBuffers, where memory is allocate outside of Java heap. This can be beneficial since this memory is not touched by garbage collector, and as such does not contribute to GC overhead: this is a very useful for property for long-living things like caches.

However, there is one critical problem with existing implementation: underlying memory is only allocated asynchronously when the owning ByteBuffer is garbage-collected; there is no way to force early deallocation. This can be problematic since GC cycle itself is not influenced by handling of ByteBuffers, and given that ByteBuffers are likely to reside in Old Generation memory area, it is possible that GC is called hours after ByteBuffer is no longer in use.

But in theory it should be possible to use sun.misc.Unsafe methods (freeMemory, allocateMemory) directly: this is what JDK itself uses for allocating/deallocating native memory. Looking at code, one potential concern I see is possibility of double-freeing of memory -- so I would want to make sure that state would be properly cleaned.

Can anyone point me to code that does this? Ideally would want to use this instead of JNA.

NOTE: I saw this question which is sort of related.

Looks like the answers pointed out are good way to go: here is code example from Elastic Search that uses the idea. Thanks everyon!

Community
  • 1
  • 1
StaxMan
  • 113,358
  • 34
  • 211
  • 239

3 Answers3

30

There is a much simpler way to clean memory.

public static void clean(ByteBuffer bb) {
    if(bb == null) return;
    Cleaner cleaner = ((DirectBuffer) bb).cleaner();
    if (cleaner != null) cleaner.clean();
}

Using it can make a big difference if you are discarding direct or memory mapped ByteBuffer fairly quickly.

One of the reasons for using the Cleaner to do this is that you can have multiple copies of the underlying memory resources e.g. with slice(). and the Cleaner has a resource count of these.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • 1
    Hmmh. I was looking at cleaner, but wasn't sure if access rights would let me call it directly... but if so, this definitely would be a good way to go. Thanks -- maybe I was too hasty in discarding this approach! – StaxMan Dec 11 '11 at 18:00
  • I have used this approach in a number of projects. Its better than using reflection IMHO. – Peter Lawrey Dec 11 '11 at 21:48
  • Right -- as long as classes are accessible, I agree. Code itself can then be loaded dynamically, if there's need to support potential case of Unsafe itself missing. Thanks again! – StaxMan Dec 13 '11 at 01:37
  • 1
    My answer does cleaner.clean through reflection. Some may prefer not need importing any sun.* packages directly. http://stackoverflow.com/a/19447758/185565 – Whome Oct 18 '13 at 10:52
  • 2
    sun.nio.ch.DirectBuffer (unlike sun.misc.Cleaner) can no longer be used by default in Java 1.9, see the JEP 260. – gouessej Sep 19 '15 at 11:37
  • 3
    Maybe to make up for that, `sun.misc.Unsafe` in Java 9 adds a `void invokeCleaner(ByteBuffer)` method. – Chapman Flack Jul 02 '17 at 21:42
5

Using sun.misc.Unsafe is hardly possible because base address of the allocated native memory is a local variable of java.nio.DirectByteBuffer constructor.

Actually you can force freeing of native memory with the following code:

import sun.misc.Cleaner;

import java.lang.reflect.Field;
import java.nio.ByteBuffer;

...

public static void main(String[] args) throws Exception {
    ByteBuffer direct = ByteBuffer.allocateDirect(1024);
    Field cleanerField = direct.getClass().getDeclaredField("cleaner");
    cleanerField.setAccessible(true);
    Cleaner cleaner = (Cleaner) cleanerField.get(direct);
    cleaner.clean();
}
szhem
  • 4,672
  • 2
  • 18
  • 30
  • Right, this is why I asked; the whole orchestration of Cleaner paired with callback to Deallocator is complex. I will try out this approach, assuming type Cleaner is accessible (I noticed that thing it calls on isn't). – StaxMan Dec 11 '11 at 18:02
  • You do not need reflection abuse for that. Just check the type and use explicit cast like in the solution Peter Lawrey proposed. – Alexander Nozik Dec 11 '19 at 11:45
2

Basically what you want is following the same semantics as using IO streams. Just like you need to close a stream once, you need to free the memory once. So you could write your own wrapper around the native calls making early freeing of memory possible

M Platvoet
  • 1,679
  • 1
  • 10
  • 14
  • 1
    Yes, at very high level -- but have you looked at details of exactly how? That's where the trouble starts: there isn't a method to do that with (direct) ByteBuffers, no close() method. And even under the hood, things are rather complicated, unfortunately. – StaxMan Dec 11 '11 at 18:01
  • Yes but you can change the game by implementing your own buffer using the unsafe. (Which probably works pretty darn good with padded memory to avoid false sharing, but that's a whole different ball game) – M Platvoet Dec 11 '11 at 18:32
  • True enough. And in my case, I fully control all access, so underlying raw storage is neither exposed nor should be danger of dangling references. – StaxMan Dec 13 '11 at 01:38