Skip to content
isdnetworks
Go back

앱이 조금씩 죽어 갔다

한참 하다 보면 앱이 꺼진다는 보고가 왔다. 언제 꺼지는지 물으니 정해진 것이 없다고 했다. 어떤 날은 20분이고 어떤 날은 한 시간이었다. 내 폰에서는 재현이 안 돼서 그 기기만의 문제로 봤다.

Table of contents

Open Table of contents

재현이 안 됐다

같은 보고가 여러 기기에서 오면서 기기 문제가 아니게 됐다. 공통점을 찾아봤다. 특정 화면이나 조작에서 꺼지는 것이 아니었다. 오래 할수록 잘 꺼진다는 것 하나만 남았다.

시간이 지날수록 무언가 쌓인다는 뜻이다. logcat 에는 잘못된 주소에 접근했다는 것만 있었다. 어디서 접근했는지가 없었다.

재현 대신 측정으로 바꿨다

죽는 지점이 매번 다르면 그 지점이 원인이 아닐 수도 있다. 주소를 쫓는 것을 접었다. adb shell dumpsys meminfo 로 얼마나 쓰이는지부터 봤다.

주기적으로 찍어 보니 그림이 명확했다. 10분과 30분과 50분에 계속 늘었다. 한 번도 줄지 않았다. 게임 화면을 나갔다 들어와도 그대로였다.

언제 죽는지는 알 수 없다. 죽기 전에 무엇이 변하는지는 잴 수 있다. 재현을 시도할 때는 진전이 없었고 측정으로 바꾸고 나서 움직였다.

어디서 새는지 좁혔다

전체가 는다는 것은 알았지만 어디서인지는 몰랐다. 화면을 하나씩 오가며 쟀다. 로비만 오가면 안 늘고 게임에 들어갔다 나오면 늘었다.

그 화면은 C++로 되어 있고 Java 에서 불러 띄우는 구조였다. 그래서 JNI 경계를 의심하고 adb shell setprop debug.checkjni 1CheckJNI 를 켰다. 켜자마자 전역 참조가 계속 는다는 경고가 나왔다. 처음부터 켜 두었으면 측정하는 동안 이미 알았을 것이다.

두 언어가 만나는 자리

Java 객체를 넘겨받아 들고 있었는데 화면을 나갈 때 놓아 주지 않았다.

void setCallback(JNIEnv* env, jobject obj) {
    callbackObj = env->NewGlobalRef(obj);   // 붙잡는다
}
void onExit() {
    // 놓는 코드가 없다
}

NewGlobalRef 로 붙잡으면 Dalvik 의 회수 대상에서 빠진다. 화면에 들어갈 때마다 하나씩 붙잡았고 나올 때 안 놓으니 계속 쌓였다. 이 표는 기본 상한이 2000이라 그것을 넘으면 앱이 그대로 죽는다. DeleteGlobalRef 를 넣으니 그래프가 평평해졌다.

여기서 이 객체는 누가 책임지는가라는 물음이 생겼다. C++에서는 내가 만든 것을 내가 지운다. Java 에서는 안 쓰면 알아서 정리된다. 두 세계가 만나는 자리에서는 한쪽이 붙잡고 있는 동안 정리되지 않는다.

정리


Share this post on:

Previous Post
예외 처리가 쌓여서 구조를 바꿨다
Next Post
중계하면서 다듬었더니 안 맞았다