The Cocoa runtime uses two modes of managing object allocation and de-allocation:
- Retain/Release (RR)
- Garbage Collection (GC)
Developers who create libraries that can be used in other applications (like Java) need to be aware and write Objective-C code that works in both modes. When Java's garbage collector is added into the mix with JNI local refs, global refs, and passing native objects up to Java as jlongs, memory management can get quite complicated!
This page assumes you are already familiar with the following concepts:
There are a few simple rules to follow when writing JNI code that handles Cocoa objects:
Always use JNF_COCOA_ENTER()/JNF_COCOA_EXIT()
- These macros ensure that an autorelease pool is always setup and popped so ObjC objects are not leaked in RR mode.
- They also catch ObjC exceptions that are thrown, and rethrow them as Java exceptions
As a rule, Java objects should own native objects in jlongs
- The jlong type is used because it is big enough to hold both 32 and 64-bit sized pointers
- Use the jlong_to_ptr() and ptr_to_jlong() macros to correctly handle casting and avoid sign-extension problems
- This keeps the number of JNI global refs HotSpot has to manage to a minimum
- Java objects should not be pinned in the Java heap until some random native retain count lowers or the ObjC-GC decides to kick in
- Java objects that own native objects have to concretely define the lifecycle of the native objects they hold
- Do not let native objects hold onto Java objects in JNI global refs, or you can create a cycle across the Java and ObjC garbage collectors, and neither collector will realize that they can deallocate their respective objects
Native objects held by Java objects must have a "hard" CF-retain count of 1 before being passed up to Java
- ObjC objects that are +alloc'd or -retained are not actually pinned in GC-mode unless they have been CFRetain()'d
- As a counterpoint, any CFRetain()'d ObjC object must be -released or -autoreleased for it's retain count to remain balanced in RR mode
- When the Java object is done with the native object, it must be explicitly "hard" CFRelease()'d
- This balances the "hard" CFRetain() which occurred before it was passed up to Java
- You need to determine if the object is safe to CFRelease() from any thread, or must only be done from the main AppKit thread
- In some cases, releasing an object may do nothing but decrease it's retain count, but in other cases it will cause the object's -dealloc or -finalize method to be called, and those may not be any-thread safe if they end up releasing AppKit objects
- The ObjC garbage collector cannot see into the Java heap, and infer if any native objects are still "alive"
Don't manage JNI global refs yourself
- If you need to pass a Java object through native (like to the main AppKit thread, or inside of an NSArray), use JNFJObjectWrapper or JNFJObjectWeakWrapper
- It handles all the details of managing a JNI global ref in a native ObjC object in both RR and GC modes
- If you have a JNIEnv when you are done with the JNFJObjectWrapper, pre-clear the reference
- It is more efficient than the wrapper's -dealloc, which may have to connect the current thread to the JVM (possibly a ObjC-GC collector thread), just for the purposes of deleting the ref.
Don't put ObjC objects into C-structs
- The ObjC garbage collector can't find objects that have been stuffed into C arrays and structs
- If you must use a struct to hold onto a ObjC object (like some pieces of the graphics code currently does), you need to pin the ObjC using CFRetain(), just like if you were going to push it up into the JVM heap.
- Be sure to CFRelease() any AppKit objects on the main AppKit thread, so their -dealloc methods will be called on the right thread!