0

Have been using java.nio.ByteBuffers on the NDK side for a while now - noticed this article about Android relationship with JNI, GC and future of ICS. Article here http://android-developers.blogspot.com/2011/11/jni-local-reference-changes-in-ics.html

So... here is the concern:

Since the "pointer" that JNI provides seems to actually be a reference that is managed by the JNI internaly - it could be "moved" or deleted by GC at some point if it is not marked as NewGlobalReference() in JNI method before being passed to c++ classes?

In my JNI methods I take the Direct Buffer address and pass it on to classes that use it, without any

env->NewGlobalRef(jobject); 
env->NewLocalRef(jobject); 
env->DeleteGlobalRef(jobject); 

management.

For now it all works - but is it correct?

Thoughts?

P.S - I do use free(ByteBuffer) on exit/destructor in c++

narkis
  • 335
  • 1
  • 7
  • 17

2 Answers2

2

A local reference is only valid for the duration of the JNI method that it is passed to or created in. After that method returns to the JVM, the reference is no longer valid. If you're not breaking that rule, you're OK.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • Well, it seems like I do break it - actually. I get the starting memory address for the java.nio.ByteBuffer from Java side, and pass it directly to be used by a c++ method. The JNI returns and the reference should be gone with it. The memory address that was passed to the c++ method is used again and again in the life cycle of the app until it is destroyed with the app on exit. – narkis Jun 26 '13 at 12:17
  • So don't do that. At least use a direct byte buffer so there is some semantic support for the assumption your code is making, – user207421 Jun 26 '13 at 22:57
1

It's a bit unclear what you're asking, so let me try to clarify a few points.

Any jobject type you get in JNI, whether returned from a JNI call like FindClass or passed in as an argument (jobject, jclass, jbyteArray, etc), is a local reference. It has a very short lifespan. If you pass it to NewGlobalRef, you get a global reference in return; this will last until you delete it.

Any JNI function that takes or returns a pointer type is giving you a pointer that's good until something invalidates it. For example, if you call GetStringUTFChars, you get a const char* that's valid until you call ReleaseStringUTFChars.

References are not pointers, and pointers are not references. You can't pass a char* to NewGlobalRef, and you can't dereference a global reference (where "can't" is usually an error or a native crash).

What I assume you're doing is calling GetDirectByteBufferAddress on a ByteBuffer object, which returns a void* that points to the start of the direct byte buffer. This pointer is valid until the storage is freed. How that happens depends upon how you allocated it:

  • If you allocated the direct byte buffer with ByteBuffer.allocateDirect(), then Dalvik owns the storage. It will be freed when the ByteBuffer becomes unreachable and is garbage collected.
  • If you allocated the storage yourself, and associated it with a ByteBuffer with the JNI NewDirectByteBuffer call, then it's valid until you free it.

For the allocateDirect() case, it's very important that your native code stops using the pointer before your managed code discards the ByteBuffer. One way to do this would be to retain a global reference to the ByteBuffer in your native code, and invalidate your buffer pointer at the same time you delete the global reference. Depending on how your code is structured that may not be necessary.

See also the JNI Tips page.

fadden
  • 51,356
  • 5
  • 116
  • 166
  • #fadden - thanks for clarification - it looks like I am safe for now - since Java side owns these direct buffers. But will keep an eye out! – narkis Jun 29 '13 at 15:23