Here is a snippet from a recent JDK 6 implementation:
#define DEFINE_GETSCALARARRAYELEMENTS(ElementTag,ElementType,Result, Tag) \
\
JNI_QUICK_ENTRY(ElementType*, \
jni_Get##Result##ArrayElements(JNIEnv *env, ElementType##Array array, jboolean *isCopy)) \
JNIWrapper("Get" XSTR(Result) "ArrayElements"); \
DTRACE_PROBE3(hotspot_jni, Get##Result##ArrayElements__entry, env, array, isCopy);\
/* allocate an chunk of memory in c land */ \
typeArrayOop a = typeArrayOop(JNIHandles::resolve_non_null(array)); \
ElementType* result; \
int len = a->length(); \
if (len == 0) { \
/* Empty array: legal but useless, can't return NULL. \
* Return a pointer to something useless. \
* Avoid asserts in typeArrayOop. */ \
result = (ElementType*)get_bad_address(); \
} else { \
result = NEW_C_HEAP_ARRAY(ElementType, len); \
/* copy the array to the c chunk */ \
memcpy(result, a->Tag##_at_addr(0), sizeof(ElementType)*len); \
} \
if (isCopy) *isCopy = JNI_TRUE; \
DTRACE_PROBE1(hotspot_jni, Get##Result##ArrayElements__return, result);\
return result; \
JNI_END
\
JNI_QUICK_ENTRY(ElementType*, \
jni_Get##Result##ArrayElements(JNIEnv *env, ElementType##Array array, jboolean *isCopy)) \
JNIWrapper("Get" XSTR(Result) "ArrayElements"); \
DTRACE_PROBE3(hotspot_jni, Get##Result##ArrayElements__entry, env, array, isCopy);\
/* allocate an chunk of memory in c land */ \
typeArrayOop a = typeArrayOop(JNIHandles::resolve_non_null(array)); \
ElementType* result; \
int len = a->length(); \
if (len == 0) { \
/* Empty array: legal but useless, can't return NULL. \
* Return a pointer to something useless. \
* Avoid asserts in typeArrayOop. */ \
result = (ElementType*)get_bad_address(); \
} else { \
result = NEW_C_HEAP_ARRAY(ElementType, len); \
/* copy the array to the c chunk */ \
memcpy(result, a->Tag##_at_addr(0), sizeof(ElementType)*len); \
} \
if (isCopy) *isCopy = JNI_TRUE; \
DTRACE_PROBE1(hotspot_jni, Get##Result##ArrayElements__return, result);\
return result; \
JNI_END
You don't have to understand all of it. Most of it is C++ macro magic. What it is is the code for the Get*ArrayElements functions. Notice that it always allocates a new array and it always copies the array. Note that this varies by VM implementation. Android's Dalvik VM never copies (to learn more see my post, JNI+NDK Arrays, Bitmaps, and Games, Oh My!). If you're trying to get the elements of a large array, that means you'll allocate twice the memory, and use extra processing power to copy the arrays. You'll also have to copy the array back when you release the array. This could lead to inefficiencies in your code. If you have a large, long lived, array that goes between the native and Java layer, you should use direct buffers instead.
You can create a direct buffer in two ways. The first way is to create the buffer from the Java layer. You do this by calling ByteBuffer.allocateDirect(). The second way is to create the buffer from the native layer. You can do this by calling NewDirectByteBuffer().
Once a buffer has been created, you can use the ByteBuffer API's to access elements of the buffer from Java. On the native side you can use GetDirectBufferAddress() to get a pointer to the buffer.
One thing to be aware of is that changes made to the direct buffer are seen immediately in the Java layer, but using conventional methods, the changes to the array are only seen when the array is released.