<Java>
1. Prerequisite & Reference
- 메모리에 대한 이해.
- Java 에 대한 기초 지식.
- C나 C++에 대한 기초 지식.
- 프로그램 경험 ( Java 뿐만 아니라 직접 메모리를 할당하는 C 나 C++ 도 경험이 있다면 더 좋다. )
2. Intro
이 녀석은 알아서 메모리를 관리해주는 녀석인데 왜 이녀석을 알아야 하나요?
Garbage Collection 또는 Garbage Collector 로 잘 알려진 이 GC 라는 녀석은 자동으로 메모리를 관리해주기 때문에
C나 C++ 에 비해 메모리를 관리하는 수고를 덜어주는 녀석이 확실합니다.
하지만 이 GC 라는 녀석이 하는 일은 매우 Heavy하고 100% "우리가 기대하는데로" 작동하진 않습니다.
이녀석도 프로그램이라 정해진 로직으로 작동할 뿐이죠.
GC에도 여러가지 방식이 있는데 방식에 따라서 Application이 잠시 Stuck ( GC 하는 동안 멈춤 ) 되기도 하고,
GC를 하는 동안 성능이 떨어지는 등의 일도 발생하지요.
또한 잘못된 메모리 관리는 GC의 로직에 벗어나며 OutOfMemoryError 도 초래합니다.
이녀석은 Exception이 아닌 Error라서 복구하기도 매우 힘듭니다.
GC에 대한 이해는 결국 Memory 관리방법에 대한 전략으로 이어지며,
개발 결과물에 대한 Performance 로 결론 지어집니다.
Best Programmer 가 아니더라도, Good Programmer 가 되려면 GC에 대한 이해는 필수죠.
3. Information
GC가 뭐하는 녀석인지 우선 간단히 소개 좀 해주세요
먼저 C와 C++을 프로그래밍 해보신 분들이라면 malloc 과 free 을 신나게 써보셨을 것입니다.
그리고 malloc과 free 가 제대로 되지 않으면 시스템이나 어플리케이션이 뻗어버리는 현상도 많이 접하셨겠죠.
사실 저는 C와 C++ 언어가 Java 보다 어렵다고 생각하는 1인입니다.
무엇보다도 메모리와 관련된 포인터라던지 malloc, free 가 가장 어렵게 느껴집니다.
얼마의 메모리가 필요한 지 알아야 하고, 확장할 때 또 계산해줘야 하고, 포인터는 &* 등의 추가 연산자를 붙여서 처리해줘야 하고,
free 해주지 않으면 메모리는 무한정 늘어나 버릴테니까 말이죠.
고민해야 할게 이만저만이 아닙니다.
이런 측면에서 pointer, malloc, free 등의 어려운 점을 한방에 "어느 정도" 해결해줄 수 있는 Java의 GC 는 정말로
프로그래밍의 혁명 자체죠.
GC는 Garbage Collection 또는 Garbage Collector 로 알려져 있으며,
참조되지 않는 메모리, 이것을 쓰레기 ( Garbage ) 로 표현하는데, 이 쓰레기 메모리들을 자동적으로 free 해주는 녀석이라고 보면 됩니다.
사실 GC는 Garbage Collection이라는 이름 때문에 보통 free 의 역할만을 하는 것으로 알고 있는데요.
요 녀석이 객체의 메모리 할당, 사용중인 메모리의 인식, 사용 하지 않는 메모리의 인식의 역할도 하고 있습죠.
즉 GC 는 pointer, malloc, free 이 세가지 + 감시(?) 모두 수행하는 녀석입니다.
이 녀석의 도움때문에 우리가 할 것은 new 로 객체 생성..
그리고.......
이것이 끝이 되어버립니다. 아주 쉽죠? 그냥 생성해서 쓰고 방치해두면 저 청소부 녀석이 알아서 청소를 해줍니다.
메모리 할당과 해제를 이녀석이 알아서 해줘버리니 말이죠.
GC 대상이 new 로 생성되는 객체들인데 이 녀석들에 대해서는 몰라도 되나요?
좋은 질문입니다. 당연히 알아야 합니다.
GC 입장에서 모든 객체는 그 나름의 생명 주기가 있습니다.
1. Created ( 생성 )
2. In use or reachable ( 사용 중 )
3. Invisible ( 사용 중 but 접근 불가 )
4. Unreachable ( 사용 되지 않음 )
5. Collected ( GC 대상이 됨 )
6. Finalized ( Finalize 를 거친 상태 )
7. Deallocated ( 메모리 해제 된 상태 )
자 이 생명주기의 각각의 단계가 무엇을 의미하는지, 무엇을 하는지를 알아봅시다.
이 녀석들에 대한 파악은 앞으로의 코딩 스타일에 많은 영향을 미칠 것이기 때문이죠.
Created 생성
먼저 객체를 위한 메모리 공간을 Heap에 할당합니다.
그 다음 Super class 의 생성자 호출하죠.
initializer 및 instance variable의 initialize를 수행 한 후.
해당 객체의 생성자를 수행합니다.
[Initializer]
In Use or Reachable 사용중
객체가 생성되어 다른 객체에 의해 참조되어 있는 상태입니다.
이 상태를 Strongly referenced 상태라고 합니다.
Invisible 사용 중 but 접근불가
모든 객체가 이 상태를 거치는 것은 아닙니다만, 눈여겨 볼 필요가 있습니다.
Invisible 상태는 Strongly referenced 는 되어 있지만, 이 녀석을 직접적으로 접근할 수 없는 상태입니다.
여기서 주의할 점은 Invisible 객체가 항상 바로 GC 대상이 되지 않는다는 것이죠.
Invisible에 대해서는 정확히 파악이 어려우니 예제를 함께 보도록 하죠.
public void run(){
Object foo = new Object();
foo.doSomething();
while( true ){
// do something.
}
}
여기서 foo 라는 녀석은 적어도 run() function 이 return될때까지는 strong reference 를 가집니다.
이 상태가 바로 invisible 상태입니다. run() 안에서 생성되어 strong reference를 가졌지만 저 녀석을 다시 접근할 수 없는 상태에서.
while 문이 끝날 때까지는 계속 메모리가 유지되는 것이죠.
따라서 memory leak 을 유발할 수 있으며, 명시적으로 null 을 만들어 주는 것이 좋습니다.
null 이 되면 strong reference 가 해지되면서 Unreachable 상태로 돌아서기 때문이죠.
Unreachable 사용되지 않음
Strong reference가 존재 하지 않을 경우이며 Unreachable 상태의 객체는 GC 의 후보가 됩니다.
여기서 후보가 된다는 것은 바로 GC 가 된다는 게 아니라, GC 대상 큐에만 들어간다는 개념입니다.
엄밀히 이야기하면 이러한 객체들은 GC의 루트가 가지는 체인에 참조가 되는 형태이며,
GC 가 수행될 때 이 루트의 체인을 따라가며 메모리 해제를 하는 식이 되는 것이죠.
[순환참조인 녀석들은 그럼 영원히 GC가 안 되는 건가요?]
Collected GC 대상이 됨
이제 메모리 해제 단계의 도입부에 왔습니다.
이 상태에서는 GC가 해당 객체의 finlize() function 이 정의되어있는지 판단을 하게 되죠.
finalize 가 있다면 finalizer 라는 queue 에 넣어놓고, 없다면 바로 다음 단계인 Finalized 상태로 전환을 시킵니다.
Finalized Finalize 를 거친 상태
Finalizer에 의해 finalize가 실행된 후의 상태입니다.
앞서 Collected 에서 설명했지만, finalize 는 Collected 상태라고 바로 수행되는 것이 아니라,
Finalizer의 Queue에 들어가는 것이기 때문에 해당 객체의 finalize 가 Call 되는 timing은 보장되지 않습니다.
또한 JVM에 따라서 수행 시간도 다르다고 하네요.
결론적으로 GC를 수행함에 있어 객체에 finalize 가 구현 되어있다면 객체의 반환시간은 그만큼 더 늦어집니다.
게다가 finalize를 위한 객체의 메모리도 그만큼 증가하겠죠. 참고적으로 재생이라는 현상도 발생할 수 있습니다.
[재생 ( Resurrection )]
Deallocated 메모리가 해제된 상태
메모리의 반환이 끝난 상태로, GC의 동작이 마무리 되었습니다.
저런 좋은 녀석이 언제 문제가 되나요?
앞서 객체의 생명주기와 함께 살펴보았듯이 GC의 기준이 되는 것은 보통 Strong Reference 의 여부입니다.
이 Strong Reference 를 불필요하게 유지한다면, GC의 타겟이 되어야 할 객체들이 GC가 되지 않은체
메모리에서 딱~ 자리를 잡고 하는 일없이 삐대고 있게 되는 것이죠.
이런식으로 메모리가 증가하게 되면 결국 사용 가능한 메모리가 다 소진되어 버립니다.
메모리가 꽉 찼을 때는 보통 다음과 같은 두가지 경우가 발생합니다.
첫번째는 가상메모리 사용입니다.
여기서 가상메모리에 대해 자세히 다루지는 않겠습니다만, 가상메모리 사용에 관한 성능저하는 누구나 알고 있을 것이라 믿습니다.
RAM <-> Hard Disk 사이의 memory swap은 생각보다 큰 operation 이죠.
두번째는 OutOfMemoryError입니다.
이 녀석은 Exception과 다르게 한번 넘어간다고 해결될 녀석이 아닙니다.
이 녀석이 발생했을 경우 메모리 해지가 제대로 일어나지 않는다면, 이 녀석은 계속 불리게 되겠죠.
물론 Error 에 대한 예외처리를 하지 않았을 경우 어플리케이션이 다운될 수 있다는 것 역시 무시무시합니다.
( Android의 경우는 메모리 확보를 위해 중요도가 낮은 process 를 강제로 죽여버립니다. )
메모리를 다 사용하지 않더라도, 미숙한 메모리 관리는 지속적인 GC로 이어지게 됩니다.
GC 자체가 매우 무거운 operation입니다.
GC의 방식에 따라서 다른 동작은 모두 hold 하고 GC만을 수행하는 경우가 있는데, 이 경우는 GC가 끝날때까지 프로그램은 멈춥니다.
다른 동작과 GC가 하는 경우도 있지만, 이 경우에는 원래 프로그램의 성능이 매우 하락되죠. 엄청 버벅댄다는 이야기입니다.
[GC 방식에 대한 짧은 소개]
결국 메모리 관리를 제대로 못하는 것은
GC라는 좋은 Tool 을 가지고도 특성을 파악하지 못해 제대로 사용하지 못하고 있는 꼴이 되는 것이죠.
게임으로 말하자면 게임에 하나뿐인 유니크 에픽 무기를 가지고도
내구도가 0 인 체로 계속 사용하고 있는 꼴이라고 볼 수 있겠습니다. 감이 확 오시나요? :)
GC를 강제로 부를 수도 있나요?
예 강제로 부르는 방법은 분명 제공하고 있습니다.
System.gc() 나 Runtime.getRuntime().gc() 를 통해서 할 수 있죠.
하지만 권장되지 않습니다. 말 했던데로 GC가 소모하는 시스템 리소스의 양이 많은데다,
잘못된 프로그래밍은 버벅대기만 하는 프로그램을 만들 수도 있기 때문입니다.
게다가 GC 나름대로 GC를 발동시키는 알고리즘 ( 또는 타이밍 ) 이 있는데, 이 알고리즘이 매우 합리적이기도 하죠.
( 초보 개발자가 다루는 것보다는 훨~~~~씬 합리적일 것입니다. )
일부 고참 개발자들은 "절대 강제호출 하지 말라!" 라고 이야기하기도 합니다.
[GC 타이밍에 대한 짧은 소개]
4. Summary
- GC ( Garbage Collection ) 은 매우 heavy 한 작업이고, "우리가 기대하는 데로" 작동하지 않기 때문에 알아둘 필요가 있다.
- GC는 C나 C++의 pointer, malloc, free 의 역할을 모두 담당해주는 녀석으로, new 를 통해 heap 메모리에 생성된 객체가
사용되지 않을 때 ( Strong reference 가 없을 때 ) 그 객체를 free 해준다.
- GC에 대해 이해하기 위해서는 객체의 생성 주기를 알아야 한다.
- 객체의 생성 주기 안에는 순환참조, Invisible, 재생, finalize 등의 매우 rare 하지만 반드시 고려하고 알아야 할 사항들이 있다.
- GC는 만병통치약이 아니며 OutOfMemoryError 나 Performance degradation으로 이어질 수 있다.
- 강제로 GC 를 부르는 행위는 암묵적으로 금지되어 있다.
- GC를 이해하고 Memory 관리를 잘 하는 것은 Good Programmer 로서 반드시 갖춰야 할 덕목이다.
5. Reference.
- http://50001.com/sub/down/GarbageCollection.pdf
Garbage Collection 에 대해 예제와 함께 심도있게 다룬다. 후반부에 가면서 좀 어렵지만 한번쯤 꼭 읽어보길 권장하다.
- [자바 성능을 결정짓는 코딩 습관과 튜닝 이야기], 이상민 저, 한빛미디어
자바의 성능에 관련된 여러가지 사항들을 소개하는 책으로 자바 중급자정도가 읽으면 좋은 책.
'프로그래밍 놀이터 > 안드로이드, Java' 카테고리의 다른 글
[Android/안드로이드] 쉽게 사용하는 Thread와 Handler. AsyncTask class 를 통해 해보아요~ (개념) (9) | 2012.01.19 |
---|---|
[Android/안드로이드] 예제를 통해 배우는 간단한 안드로이드 AppWidget. 함께 만들어 봐요 #2 (5) | 2012.01.14 |
[Android/안드로이드] 개념을 통해 배우는 간단한 안드로이드 AppWidget. 함께 만들어 봐요. #1 (0) | 2012.01.14 |
[Java] String 을 + 로 연결하는 건 이제 그만! StringBuilder 로 간단하게 메모리와 퍼포먼스를 동시에 잡아보자. (2) | 2012.01.12 |
[Java] System Class 한번 제대로 써보자. 이제 당신도 JAVA 중급 개발자 (0) | 2012.01.12 |
[Java] Garbage Collection ( GC ) 가 뭔가요? GC 기초부터 고급까지! (7) | 2012.01.12 |
비밀댓글입니다
답글
네 출처만 잘 밝혀주세요 ^^
우아~ 너무너무 잘밨떠여~
답글
gc에 대한 어렴풋하던 개념이 좀 더 이해가 되는 글이었습니다.
java의 경우 gc가 자동으로 쓰레기 처리를 해준다고 알고 있었는데...
생애주기가 있는? 음.. 글을 쓰다보니 잘 이해를 못한것 같기도 하군여. 흠...
fileUtils를 이용하여 디렉토리 이동시에 IOException이 발생하여 gc를 사용했는데(사용하고 나서 잘 되었으나)
여러번 반복실행시에는 경고로 추정되는 메시지가 계속 나와서 써도 되는것인가?라고 했는데
역시 맘편히 쓰지 못하겠단 생각이 드네요. 투머치토크가 될 것 같아 줄입니다~
포스팅 잘 보고 갑니다!
답글
ㅎㅎ 넵, 참고로 IOException 과 gc 는 큰 연관은 없고요. gc 를 강제로 호출하는것도 지양되는편입니다. 참고하세요!
혹시 그럼 movedirectoryToDirectory하는데 파일들이 사용중에 걸리는 이유는 아시나요?
30개이상 파일들이 있는 디렉토리 무브시 ioexception이 발생됩니다.
이때문에 카피만 하고 몇시간뒤 삭제하는걸로 바꾸는걸로 해결하긴했는데
답글
실제 파일들이 사용중일 수도 있고, 다른 방향으로 접근해야 할 것 같습니다. ioexception 의 cause 를 봐야할거같아요