- 모든 reference 가 모든 context 에 통용되는 것은 아닙니다. 그 scope 를 잘 알고 있어야 합니다.
Local and Global 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 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 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 도 살아 있습니다.
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 하고 있다는 의미입니다.
원래의 object 가 차지하는 메모리에 덧붙여, JNI reference 역시 메모리를 소모합니다. 그래서 프로그래머는 메모리 소모에 주의해야 합니다. 물론 JVM 이 local reference 들에 대해서는 함수 return 시에 메모리 해제를 자동으로 수행하지만, array를 loop 로 돈다던지 하는 행위는 OutOfMemory 로 이어질 수 있습니다.
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 안에서 만들어졌죠. )
댓글