Skip to content
isdnetworks
Go back

자바에서 받은 문자열을 안 놓아 줬다

Cocos2d-x 쪽에서 기기 정보가 필요해 JNI 로 Java 를 부르는 코드를 만들었다. 잘 동작했는데 오래 켜 두면 느려졌다.

Table of contents

Open Table of contents

받기만 하고 있었다

문자열을 얻는 부분이 이랬다.

const char* c = env->GetStringUTFChars(js, NULL);
std::string result(c);
return result;

GetStringUTFChars 는 Java 힙에서 버퍼를 받아 오는 것이다. 짝인 ReleaseStringUTFChars 가 빠져 있었다. 부를 때마다 버퍼가 쌓이고 아무 오류도 안 난다.

지역 참조도 쌓인다

문자열 버퍼만 문제가 아니었다. FindClassCallObjectMethod 가 돌려주는 jobject 도 지역 참조로 남는다.

지역 참조는 네이티브 함수가 Java 로 돌아갈 때 정리된다. 그 안에서 반복문을 돌면 그때까지 계속 쌓인다.

Dalvik 의 지역 참조 표는 512 칸이다. 넘기면 이렇게 죽는다.

JNI ERROR (app bug): local reference table overflow (max=512)
Failed adding to JNI local ref table (has 512 entries)

목록을 그리면서 반복해 부르다 실제로 만났다.

정리를 소멸자로 옮겼다

정리 호출을 넣으니 함수가 눈에 띄게 길어졌다. 중간에 빠져나가는 경로마다 정리를 해야 하기 때문인데 그렇게 여러 곳에 흩어 놓으면 언젠가 하나를 빠뜨린다.

class LocalRef {
public:
    LocalRef(JNIEnv* e, jobject o) : env(e), obj(o) {}
    ~LocalRef() { if (obj) env->DeleteLocalRef(obj); }
    jobject get() { return obj; }
private:
    JNIEnv* env;
    jobject obj;
};

JNI 에 PushLocalFramePopLocalFrame 도 있었다. 그 사이에서 만든 지역 참조를 한 번에 놓는다. 다만 감싸는 쪽이 소멸자라 어느 경로로 나가도 불린다는 점이 나아서 위쪽으로 갔다.

범위를 벗어나면 소멸자가 DeleteLocalRef 를 부른다. 중간 어느 지점에서 돌아가도 빠뜨릴 수 없다. GetStringUTFChars 쪽도 같은 방식으로 감쌌다.

사람이 기억해서 부르는 것과 구조가 대신 부르는 것은 다르다. 앞의 것은 경로가 늘어날수록 빠뜨릴 확률이 올라가고 뒤의 것은 경로가 늘어도 그대로다.

예외 확인과 반복 호출

Java 쪽에서 예외가 나도 네이티브 코드는 그냥 진행한다. 예외가 걸린 상태로 다른 JNI 호출을 하면 안 된다. 확인하고 지우는 처리를 넣었다.

if (env->ExceptionCheck()) {
    env->ExceptionDescribe();
    env->ExceptionClear();
    return "";
}

adb shell setprop debug.checkjni 1 을 켜니 JNI 실수마다 로그가 붙었다. 그리고 같은 함수를 천 번 반복해서 불러 봤다. 정리를 넣기 전에는 500 회쯤에서 죽었고 넣은 뒤에는 천 회를 넘겼다. 반복해서 불러 보는 것이 이런 문제를 가장 빨리 드러냈다. 한 번 부르면 아무 문제가 없어 보인다.

정리


Share this post on:

Previous Post
배포는 됐는데 아무것도 안 바뀌었다
Next Post
물려받은 규칙을 확인 없이 따랐다