본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[Java] JNI Tutorial - Local and Global References

by 돼지왕 왕돼지 2012. 3. 30.
반응형



안녕하세요 돼지왕 왕돼지입니다.

오늘은 JNI 중 Local Reference 와 Global Reference 에 대해 알아보겠습니다.

이 글은 http://java.sun.com/docs/books/jni/html/refs.html#27423 내용을 요약 정리한 내용입니다.

Local and Global References

JNI는 jobject, jclass, jstring, jarray 등을 지원을 하는데 그것들은 opaque reference 입니다. 그래서 이녀석들은 JNI 함수를 통해서 opaque reference 의 내용물들을 받아와야 합니다. 이 opaque reference 의 장점은 JVM 의 내부구현에 상관없이 native code 를 구현할 수 있다는 것입니다. 프로그래머는 JNI 가 지원하는 다음의 reference 종류와 그 특징들을 알고 있어야만 합니다.

- JNI는 local reference, global reference, weak global rference 라는 3가지 reference 를 제공합니다.


- local reference 는 native 함수 return시 자동으로 free 되고, global 과 weak global reference 는 프로그래머가 free 할때까지 reference 가 유지됩니다.

- local & global reference 는 reference 를 유지하여 해당 object가 GC 되는 것을 막습니다. 하지만 weak global reference는 reference 가 유지되어도 GC의 대상이 됩니다.
 
- 모든 reference 가 모든 context 에 통용되는 것은 아닙니다. 그 scope 를 잘 알고 있어야 합니다.


 

Local and Global References


Local References


NewObject 함수는 새로운 instance 를 만들고 그에 대한 local reference 를 return 합니다. 이 local reference 는 Java 의 일반적인 local variable 과 그 생명주기가 같습니다. 즉 local reference를 정의한 함수가 return 하면 그 reference 는 free 됩니다.

절대로 local reference 를 static variable 에 저장하고, 그것을 계속 사용해서는 안됩니다. 예를 들어 다음과 같은 사용은 에러를 초래합니다.

jstring
 MyNewString(JNIEnv *env, jchar *chars, jint len) {
     static jclass stringClass = NULL;
     jmethodID cid;
     jcharArray elemArr;
     jstring result;
 
     if (stringClass == NULL){
         stringClass = (*env)->FindClass(env,"java/lang/String");
         if (stringClass == NULL)
             return NULL; /* exception thrown */
     }
     /* It is wrong to use the cached stringClass here, because it may be invalid. */
     cid = (*env)->GetMethodID(env, stringClass, "<init>", "([C)V");
     ...
     elemArr = (*env)->NewCharArray(env, len);
     ...
     result = (*env)->NewObject(env, stringClass, cid, elemArr);
     (*env)->DeleteLocalRef(env, elemArr);
     return result;
 }


return 후에 이 함수안에서 생성된 모든 local reference 는 free 됩니다. 그래서 static 으로 저장된 stringClass 의 reference 도 free 됩니다. 그래서 다시 사용하게 되면, memory corruption 또는 system crash 를 유발합니다.
 

Local reference 를 해지하는 방법은 2가지가 있습니다. 먼저 local reference 를 만든 함수가 return 을 하는 것이 있고, 두번째는 DeleteLocalRef 함수를 통해서 return 전에 reference 를 해지시켜주는 것입니다. return 전에 local reference 를 삭제해주는 것은 사용 후 바로 GC 대상이 되도록 하여 메모리 누수를 막기 위함입니다.
 

return value로 local reference 가 전달 되는 case는 JVM 에 전달되는 순간, 또는 native code 에서 사용이 끝나는 순간에 GC 대상이 됩니다.
 

Local reference 는 또한 그것을 만든 thread 에서만 유효합니다. 다른 thread 에서는 쓰일 수 없습니다. Local reference 를 global variable 에 저장하거나, 다른 thread 가 쓸 수 있도록 하는 것은 에러입니다.
 


Global References


Global reference 는 multiple thread 에서 사용될 수 있으며, 프로그래머에 의해서만 free 가 됩니다. Global reference 역시 해당 object를 GC 의 대상에서 벗어나게 합니다.
 

Local reference 를 생성하는 방법은 여러가지가 있지만, Global reference 를 만드는 방법은 하나밖에 없습니다. NewGlobalRef 함수 입니다. Global reference 를 사용하면 cache 의 역할을 할 수 있습니다.

/* This code is OK */
 jstring
 MyNewString(JNIEnv *env, jchar *chars, jint len)
 {
     static jclass stringClass = NULL;
     ...
     if (stringClass == NULL) {
         jclass localRefCls = (*env)->FindClass(env, "java/lang/String");
         if (localRefCls == NULL) 
             return NULL; /* exception thrown */

         /* Create a global reference */
         stringClass = (*env)->NewGlobalRef(env, localRefCls);
 
         /* The local reference is no longer useful */
         (*env)->DeleteLocalRef(env, localRefCls);
 
         /* Is the global reference created successfully? */
         if (stringClass == NULL)
             return NULL; /* out of memory exception thrown */
     }
     ...
 }


여기서도 memory 할당 문제로 항상 NewGlobalRef 값이 NULL 이 아닌지 체크해야 합니다.


Weak Global References

 
Weak Global Reference 는 SDK 1.2 부터 등장했습니다. 이 WeekGlobalReference 는 NewGlobalWeakRef 함수를 통해서 생성되고 DeleteGlobalWeakRef 함수를 통해서 지울 수 있습니다. 이녀석은 일반적인 Global Reference 와 같이 다른 thread 또는 function 에서 자유롭게 쓰일 수 있습니다. 하지만 Global reference 와는 다르게 Weak global reference 는 GC 의 대상입니다. 위의 예제에서는 static 변수 cache 를 할 때 GlobalReference 를 쓰던 WeakGlobalReference 를 쓰던 상관이 없습니다. 왜냐하면 java.lang.String 은 system class 로서 절대 GC 대상이 되지 않기 때문입니다. Weak Reference 는 GC 가 되도 상관없는 caching 을 할 때 유용합니다. 일반 Weak 이긴 해도 Global 로 reference 하고 있으니 해당 target 이 GC 되기 전까지는 이 reference 도 살아 있습니다. 


Comparing References


Reference 들이 있을 때, 그것들이 똑같은 녀석을 point out 하고 있는지를 체크하는 방법이 있습니다.

(*env)->IsSameObject( env, obj1, obj2 );

이 녀석은 JNI_TRUE( 1 ) 또는 JNI_FALSE ( 0 )을 리턴합니다.

NULL reference 는 JVM 의 null object 를 point 합니다. object 가 NULL 인지 체크하려면 다음과 같이 합다.

(*env)->IsSameObject( env, obj, NULL ) 또는 obj == NULL


 
 WeakGlobalReference 의 경우는 살짝 다릅니다. WeakGlobalReference 가 유효한지 보려면,

(*env)->IsSameObject( env, weakObj, NULL ); 

 
을 통해서 확인합니다. JNI_TRUE 라면 이미 GC 된 reference 를 point 하고, JNI_FALSE 를 return 하면 아직 live 한 object 를 point 하고 있다는 의미입니다.



Freeing References


원래의 object 가 차지하는 메모리에 덧붙여, JNI reference 역시 메모리를 소모합니다. 그래서 프로그래머는 메모리 소모에 주의해야 합니다. 물론 JVM 이 local reference 들에 대해서는 함수 return 시에 메모리 해제를 자동으로 수행하지만, array를 loop 로 돈다던지 하는 행위는 OutOfMemory 로 이어질 수 있습니다. 

Freeing Local References


JVM 이 함수 return 시에 자동으로 local reference 들을 free 해주지만, 다음과 같은 경우에는 프로그래머가 명시적으로 free 를 해주어야 합니다.

- 한 native method 에서 엄청난 양의 local reference 를 생성하는 경우. 이 경우 JNI local reference table 의 overflow 를 초래할 수 있습니다. 이럴 경우 사용이 끝난 녀석은 바로 해제시켜 주는 것이 좋지요.

for (i = 0; i < len; i++) {
     jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);
     ... /* process jstr */
     (*env)->DeleteLocalRef(env, jstr);
 }

 
 
- 어디서 호출할지 모르는 utility function 의 경우는 DeleteLocalRef 를 통해서 바로 local reference 를 제거해주는 것이 좋습니다.

- native method 가 return 을 하지 않을 때. ( while( true ) 같은 녀석 )

- native method 가 큰 사이즈의 object 를 access 할 때, 쓴 직후 해제.


Managing Local References in Java 2 SDK Release 1.2


JDK 1.2 이후부터는 EnsureLocalCapacity, NewLocalRef, PushLocalFrame, PopLocalFrame 과 같은 LocalReference 관련 함수들을 제공합니다.
JVM 은 각각의 native method 가 16개의 local reference 를 만드는 것까지는 자동으로 허용합니다. 16개가 넘는 Local reference를 쓰고 싶을 때는 EnsureLocalCapcity 를 호출해서 그 갯수를 늘려주어야 합니다.

/* The number of local references to be created is equal to
    the length of the array. */ 
 if ((*env)->EnsureLocalCapacity(env, len)) < 0) {
     ... /* out of memory */
 }
 for (i = 0; i < len; i++) {
     jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);
     ... /* process jstr */
     /* DeleteLocalRef is no longer necessary */
 }

 
Push/PopLocalFrame 역시 프로그래머가 local reference 의 nested scope 를 만들 수 있도록 도와줍니다. 

#define N_REFS ... /* the maximum number of local references used in each iteration */
 for (i = 0; i < len; i++) {
     if ((*env)->PushLocalFrame(env, N_REFS) < 0) {
         ... /* out of memory */
     }
     jstr = (*env)->GetObjectArrayElement(env, arr, i);
     ... /* process jstr */
     (*env)->PopLocalFrame(env,  NULL);
 }


PushLocalFrame 은 특정 수를 위한 공간을 만들고, PopLocalFrame 을 호출하면 그 공간에 할당된 local reference 를 모두 해제합니다. Push/PopLocalFrame 은 local reference 의 lifetime 을 크게 신경쓰지 않아도 된다는 장점이 있습니다.

NewLocalRef 함수는 local reference 를 return 할 때 유용합니다.

PushLocalFrame, EnsureLocalCapcity 등의 호출 없이 16개 이상의 local reference 를 사용하게 되면.. 그래도 JVM 은 할당을 하긴 합니다. 다만, 이 결과는 신용할 수 없습니다. 심지어 VM 은 메모리 할당에 실패하면 종료하기도 합니다. 그러므로 분명하게 16개를 넘게 사용할 경우 적당한 함수를 사용하든가, 아니면 16개가 넘기전에 계속 free 를 해주어야 합니다.


Freeing Global References


GlobalReference 도 더 이상 사용하지 않는다면 DeleteGlobalRef 를 호출해주어야 합니다. 
WeakGlobalReference 의 경우는 DeleteWeakGlobalRef 를 호출해줘야 겠습니다. WeakGlobalReference 에 대해서는 어차피 GC 대상이기 때문에 꼭 호출해줄 필요성을 못 느낄 수도 있지만, 이 reference 자체가 소모하는 memory 는 예방할 수 없습니다.



Rules for Managing References


- native method 를 작성할 때는 loop or infinite loop에서 array object의 reference 를 만들면서 지속적으로 참조하거나 만드는 행위는 피해야 합니다. 
- local reference 의 경우 16개를 넘기지 않는 것이 좋으며, GlobalReference & WeakGlobalReference 의 경우는 쓰지 않을 때 반드시 해제해야 합니다. 
- native utility function 을 사용할 때에는 어떤 local reference 도 남겨서는 안된다는 사실을 반드시 인지해야 합니다. native utility function 은 어떤 context 에서 호출될지, 얼마나 자주 호출될지 알 수 없기 때문입니다.

primitive type 이든 reference type 이던, return 전에 모든 local, global, weak global reference 를 절대 쌓아두어서는 안됩니다. reference type 을 return 할때는 그 reference type 역시 쌓아두어서는 안 됩니다. (경우에 따라서 global & weak global reference 생성은 허용되나 정확한 의도일 때의 경우이며, 쌓여서는 안 됩니다. )

reference type 을 return 할 때, 프로그래머는 어떤 종류의 reference 를 return 하는지 반드시 알고 있어야 합니다. 그래야 관리를 할 수 있습니다.

while (JNI_TRUE) {
     jstring infoString = GetInfoString(info);
     ... /* process infoString */
     
     ??? /* we need to call DeleteLocalRef, DeleteGlobalRef, or DeleteWeakGlobalRef depending on the type of reference returned by GetInfoString. */
 }


이것을 막기 위해서는 (*env)->NewLocalRef( env, [GlobalReference] ); 를 통해 local reference를 만들어 return 하는 것도 좋은 방법입니다.

native function 도입부에 PushLocalFrame 을 사용하고, return 전에 PopLocalFrame 을 사용하는 것은 local reference 를 관리하기에 매우 좋습니다.권장되는 사용법입니다. 단, return 부에서 PopLocalFrame 을 빼먹으면 memory leak 으로 VM 이 crash 될 수 있겠죠.

jobject f(JNIEnv *env, ...) {
     jobject result;
     if ((*env)->PushLocalFrame(env, 10) < 0) {
         /* frame not pushed, no PopLocalFrame needed */
         return NULL; 
     }
     ...
     result = ...;
     if (...) {
         /* remember to pop local frame before return */
         result = (*env)->PopLocalFrame(env, result);
         return result;
     }
     ...
     result = (*env)->PopLocalFrame(env, result);
     /* normal return */
     return result;
 }


PopLocalFrame 의 2번째 argument는 해당 reference 를 new local reference 로 만듭니다. (해당 예제에서 result 는 frame 안에서 만들어졌죠. ) 



반응형

댓글