I am using JCUDA and would like to know if the JNI objects are smart enough to deallocate when they are garbage collected? I can understand why this may not work in all situations, but I know it will work in my situation, so my followup question is: how can I accomplish this? Is there a "mode" I can set? Will I need to build a layer of abstraction? Or maybe the answer really is "no don't ever try that" so then why not?
EDIT: I'm referring only to native objects created via JNI, not Java objects. I am aware that all Java objects are treated equally W.R.T. garbage collection.
There are two cases here.
Usually, such libraries do not deallocate memory due to garbage collection. Particularly: JCuda does not do this, and has no option or "mode" where this can be done.
The reason is quite simple: It does not work.
You'll often have a pattern like this:
Here, native memory is allocated, and the Java object serves as a "handle" to this native memory.
At the last line, the
data
object goes out of scope. Thus, it becomes eligible for garbage collection. However, there are two issues:1. The garbage collector will only destroy the Java object, and not free the memory that was allocated with
cuMemAlloc
or any other native call.So you'll usually have to free the native memory, by explicitly calling
before leaving the method.
2. You don't know when the Java object will be garbage collected - or whether it will be garbage collected at all.
A common misconception is that an object becomes garbage collected when it is no longer reachable, but this is not necessarily true.
As bmargulies pointed out in his answer:
It may look like a viable option to simply override the
finalize()
method of these "handle" objects, and do thecuMemFree(this)
call there. This has been tried, for example, by the authors of JavaCL (a library that also allows using the GPU with Java, and thus, is conceptually somewhat similar to JCuda).But it simply does not work: Even if a Java object is no longer reachable, this does not mean that it will be garbage collected immediately.
You simply don't know when the
finalize()
method will be called.This can easily cause nasty errors: When you have 100 MB of GPU memory, you can use 10
CUdeviceptr
objects, each allocating 10MB. Your GPU memory is full. But for Java, these fewCUdeviceptr
objects only occupy a few bytes, and thefinalize()
method may not be called at all during the runtime of the application, because the JVM simply does not need to reclaim these few bytes of memory. (Omitting discussions about hacky workarounds here, like callingSystem.gc()
or so - the bottom line is: It does not work).So answering your actual question: JCuda is a very low-level library. This means that you have the full power, but also the full responsibilities of manual memory management. I know that this is "inconvenient". When I started creating JCuda, I originally intended it as a low-level backend for an object-oriented wrapper library. But creating a robust, stable and universally applicable abstraction layer for a complex general-purpose library like CUDA is challenging, and I did not dare to tackle such a project - last but not least because of the complexities that are implied by ... things like garbage collection...
Java objects created in JNI are equal to all other Java objects, and are garbage collected and destroyed when their time comes. To keep such objects from being destroyed too early, we often use JNI function
env->NewGlobalRef()
(but its usage is by no ways limited to objects created in native).On the other hand, native objects are not subject to garbage collection.