땀이 삐질삐질 나는 개발 일기

[안드로이드] 익명클래스가 메모리릭을 유발하는 EU(이유) 본문

개발 Tip

[안드로이드] 익명클래스가 메모리릭을 유발하는 EU(이유)

삐질 2020. 3. 12. 20:43

 

안녕하세요. 삐질삐질 개발하는 개발자 삐질입니다.
오늘 글로써 소개할 내용은, 흔히들 알고있는 익명클래스와, 메모리릭에 관련 된 내용입니다.
최근 오픈 커뮤니티에서 활동하면서 많이 듣는 말 중에 하나입니다.
"익명클래스로 리스너를 등록하면 메모리릭이 나니까 익명클래스로 하지마"
"왜냐면 익명클래스는 명시적으로 해제 할 방법이 없기 때문이지"
"그래서 무조건 익명클래스로 뭔갈 하면 메모리릭이 나는거야"

혹시 글을 읽고 계신 독자님도 이렇게 생각하고 계셨나요? 그렇다면... 오른손을 듭니다.... 그러고 얼굴과 가깝게 손을 듭니다.그대로 자신의 뺨을 때립....  농담이구요^^ ...

결론적으로 익명클래스는 정적클래스로 선언하지 않아서 메모리 릭을 유발하는 것은 절대 아닙니다.
익명클래스를 쓴다고 해서 반드시 메모리릭을 발생시키는 것도 아니구요

이렇게 알고 계신다면 정말 잘못 알고계시는 부분 입니다.익명클래스가 메모리릭을 유발한다고 하는 것은 다름아닌  외부 클래스에 대한 참조를 끊지 못하기 때문입니다.명시적으로 해제하고 못하고 그런 이유가 아니에요 . 자바에서의 메모리 릭이란 일단 메모리가 할당되고 회수되는 절차부터 간단하게나마 설명드리고 지나가겠습니다.자바에는 어떤 인스턴스가 할당되면,  heap이라는 메모리 영역에 선언된 인스턴스를 정의하게 됩니다.이 heap이라는 메모리 영역에 많은 객체들이 정의 되겠죠? 때로는 heap영역의 객체들 끼리도 서로를 참조하게 됩니다.이때 우리가 잘 작성한 코드라면, 안드로이드 기준으로, heap영역의 메모리가 부족할 때 GC가 호출되어 쓰지 않는 인스턴스들의 메모리 할당을해제하게 됩니다. 이때 해제하는 기준은 해당 인스턴스가 누군가에 의해 참조가 되고있냐? 없냐? 로 판단해 없다면 해제하게 됩니다.아직 누가 참조하고 있는데 해제를 해버린다면 예기치 못한 에러가 발생할 수 있겠죠? 정상적인 로직은 이렇습니다 만, 우리가 조심해야할 것은 메모리 릭인거죠. 메모리 릭이란 위의 설명처럼

GC는 더 이상 참조가 되지 않는 인스턴스만을 회수 합니다. 그렇다면, 어딘가에서 참조가 되고있다면???
"해제하지 않겠죠."
그런데 우리가 어플리케이션을 만들고 실행하게 된다면, 해당 어플리케이션은 무한정 메모리를 할당받을 수 없습니다.

안드로이드에서는 달빅 머신쪽에서 여차 저차한 로직에 의해 일정량의 가용 메모리를 할당받게 됩니다. 즉 유한하다는 말이죠.이런 상황에서 , 더이상 쓰지 않는 메모리지만, 해제되며 메모리가 확보되지 못 하고 남아있다면 정작 새로 어떤 로직이 수행될 때 메모리가 필요한데더이상 가용 메모리도 없고, 해제 할 메모리도 없다면 내가 기대한 작업이 보장되지 않겠죠??그렇기 떄문에 메모리 릭을 발생하지 않도록 코드를 작성하는 것이 굉장히 중요합니다.다시 익명클래스로 돌아와서  코드와 함께 살펴보겠습니다.

 
 
public class TestClass {
    public void testMethod() {
        TestLeakClass testLeakClass = new TestLeakClass(new Runnable() {
            @Override
            public void run() {
                int a = 0;
            }
        });
        testLeakClass.start();
 
 
        TestInner testInner = new TestInner();
        testInner.start();
    }
 
 
    static class TestInner extends Thread {
        @Override
        public void run() {
            super.run();
            int a = 0;
        }
    }
 
 
    static class TestLeakClass extends Thread {
        public TestLeakClass(Runnable runnable) {
            super(runnable);
            int a = 0;
        }
    }
}

위와같이 정의를 해 두었을때 TestInner 클래스는 릭에 대해 안전합니다. 하지만 TestLeakClass는 안전하지 못 합니다.이유는 TestInner 클래스는 Outer클래스 즉 TestClass에 대한 참조가 없기 때문이고, TestLeakClass는 TestLeakClass를 생성 할 때 생성자에 넘겨준"new Runnable()" 이 녀석 때문입니다.익명 클래스는 필히 구현부를 만들어야하고, 그 구현부는 또 필히 Outer 클래스 내부에 속하게 됩니다. 고로 new Runnable ->익명클래스 인스턴스는 

Outer클래스인 TestClass와 참조관계가 형성되어 버리죠 .

만약 외부에서 TestClass test = new TestClass();  , test.testMethod();  이렇게 호출을 했다면 test인스턴스가 할일을 마치고 해제되어야 하는 타이밍에 new Runnable이 아직 종료가 안된 상황이라면  new Runnable이 Outer에 참조를 가지고 있기 때문에 해제 할수 없는 상황이 발생하게 됩니다. 그렇게되면 이것이 바로 메모리릭으로 이어지지요.이때 new Runnable이 종료가 안된 상황이라는 것은 가령 이런 상황입니다.

1.타이머가 내부에서 계속 돌아간다거나
2.아직 로직 자체가 끝나지 않은 경우  등등

이런 상황이기에 오로지 익명으로 선언했기 때문에 , 명시적으로 프로그래머가 해제할 수 없다. 이런 이유에서의 메모리릭이 아니라참조관계가 형성이 되고, 해당 참조가 아직 지워질 수 없는 상황이 만들어졌다. 그렇기 때문에 메모리릭을 유발한다  라고 보는 것이 맞습니다.

내부 클래스를 static class로 선언하는 이유도 이와 같습니다. 내부 정적클래스로 선언 한다는 것은 , 외부 Outer클래스를 참조하지 않는 클래스 임을 지칭하는 예약어 입니다.
 
 
이로써 익명 클래스를 다룸으로 생기는 메모리릭의 정확한 이유를 알아보았습니다.  참고하시어 즐거운 개발 하시길 바라겠습니다
( GC가 돌기 전에, 익명클래스로 선언 했더라도 이미 로직이 끝나 참조를 하지 않는다면 회수 됩니다욧) 
 
 
초급 안드로이드 개발자를 위한 카카오톡 오픈 채팅방을 운영 중 입니다. 
(저는 마냥 친절한 방장은 아닙니다.  다만, 다 같이 성장하고 싶은 방장이며 개발자 입니다)

 

Comments