티스토리 뷰

GC는 자동으로 메모리를 관리해주는 녀석이다 즉 C,C++에 비해 메모리 관리하기가 수월하다


하지만 내가 원하는대로 작동하지 않는다


GC는 보통 free의 역할만을 하는 것으로 알고 있지만 정확하게는 객체의 메모리할당,사용중인 메모리의인식,


사용하지 않는 메모리의 인식 역할도 하고있다


그래서 GC대상이 되는것은 new로 생성되는 객체들이다


따라서 new로 생성되는 객체들에대해서 알아둘 필요가 있다.


GC입장에서 모든 객체는 그나름의 생명주기가 있다


1. Created(생성)

2. In use or reachable(사용중)

3. Invisible(사용중이지만 접근불가)

4. Unreachable(사용되지않음)

5. Collected(GC 대상)

6. Finalized (Finalize를 거친 상태)

7. Deallocated(메모리 해제 된 상태)


하나하나 차근차근 알다보면


1.Created

먼저 객체를 위한 메모리 공간을 Heap에 할당

그다음 super class의 생성자로 호출

initializer 및 instance variable의 initialize를 수행한후 해당 객체의 생성자를 수행


*initializer

Class 코드의 Global Area에 선언된 { } 사이에 기술된 코드들을 말함
놀라운 사실은 이 initializer 들이 constructor ( 생성자 ) 보다도 먼저 호출됨


*heap

프로그램을 실행시키면 운영체제는 우리가 실행시킨 프로그램을 위해 메모리 공간을 할당한다

할당되는 메모리 공간은 크게 스택,힙,데이터영역으로 나뉘어진다

데이터 영역은 전역변수와 static변수가 할당되는 영역으로 프로그램 시작과 동시에 할당되고 프로그램이 종료되어야 메모리에서 소멸된다

그이유는 main함수가 호출되기 전에 데이터 영역에 할당되고 그렇기 때문에 프로그램이 종료될때까지 메모리상에 존재한다

스택영역은 함수 호출시 생성되는 지역 변수와 매개변수가 저장되는 영역으로 함수 호출이 완료되면 사라진다

힙영역은 필요에 의해 동적으로 메모리를 할당 할 때 사용한다 힙 영역은 프로그래머가 할당을 한다

그렇다면 언제 할당을 해야할까? 배열을 예로 들어 설명하자면 배열을 선언할때 상수로 선언을한다


배열의 길이를 사용자가 입력한 숫자로 잡아주는것은 비정상적인 배열선언이다 그이유는 스택영역에 할당될 메모리 크기는 컴파일타임에 결정된다

정상적인 배열 선언의 경우 arr이라는 배열의 크기가 40바이트라는것을 알 수 있다 

하지만 비정상적인 배열선언의 경우 i 크기가 4바이트라는것을 알 수 는 있으나 arr이라는 배열의 크기는 알 수 없다

그렇다면 다음과 같은 배열을 선언할 때는 문제가 없는걸까?


컴파일을 하는 동안 i가 4바이트라는 것을 알 수 는 있으나 그 값이 10으로 초기화 되었다는 사실은 무시하고 넘어간다 10으로 초기화 되었다는 사실은

런타임에 결정된다 그렇기 때문에 컴파일러는 arr의 크기가 40바이트가 된다는 사실을 알 수 없다


사용자의 요구에 맞게 메모리를 할당해 주기 위해서는 즉 런타임에 메모리 크기를 결정하고 싶을때 메모리 동적할당을 통해 힙 영역에 메모리를 할당해야 한다 다시말해 힙영역은 할당해야 할 메모리의 크기를 프로그램이 실행되는 동안 결정해야 하는 경우 유용하게 사용되는 공간이다


2. In Use or Reachable 

객체가 생성되어 다른 객체에 의해 참조되어 있는 상태

이상태를 Strongly referenced상태라고 한다


3.invisible

모든 객체가 이 상태를 거치는 것은 아니다

invisible상태는 Strongly referenced는 되어 있지만 이 녀석을 직접적으로 접근할 수 없는 상태이다

여기서 주의할 점은 invisible 객체가 항상 바로 GC의 대상이 되지 않음

예를 들면

여기서 foo라는 녀석은 적어도 run() function이 return될때가지는 Strongly referenced를 가진다

이상태가 바로 invisible상태이다 run()안에서 생성되어 Strongly referenced를 가졌지만 저 녀석을 다시 접근할 수 없는 상태에서

while문이 끝날 때까지는 계속 메모리가 유지됨 따라서 memory leak을 유발할 수 있으며 명시적으로 null을 만들어 주는 것이 좋다

null이 되면 Stringly referenced가 해지되면서 Unreachable상태로 돌아서기 때문이다


4.Unreachable

Strongly reference가 존재하지 않을 경우이며 Unreachable상태의 객체는 GC의 후보가 된다

여기서 후보가 된다는 것은 바로 GC가 된다는게 아니라 GC대상 큐에만 들어간다는 개념이다

엄밀히 말하면 이러한 객체들은 GC의 루트가 가지는 체인에 참조가 되는 형태이며 GC가 수행될때 

이 루트의 체인을 따라가며 메모리를 해제하는 식이다

그리고 순환참조인 녀석들은 서로가 서로에게 Strongly referenced를 보장하기 때문에 GC가 안될거 같지만 이런 녀석들도 GC

가 파악하여 Unreachable상태로 표시한다


5.Collected

이 상태에서는 GC가 해당 객체의 finalize() function이 정의되어 있는지 판단을 하게 된다

finalize가 있다면 finalizer라는 큐에 넣어놓고 없다면 바로 다음단계인 Finalized상태로 전환을 시킨다


6.Finalized

Finalizer에 의해 finalize가 실행된 후의 상태

앞서 Collected에서 설명했지만 finalize는 Collected상태라고 바로 수행되는 것이 아니라 finalizer의 큐에 들어가는 것이기 때문에 해당 객체의

Finalize가 call되는 Timing은 보장되지 않는다 또한 JVM에 따라서 수행시간도 다르다

결론적으로 GC를 수행함에 있어 객체에 Finalize가 구현 되어있다면 객체의 반환시간은 그만큼 더 늦어진다

게다가 finalize를 위한 객체의 메모리도 그만큼 증가한다 또한 Resurrection현상도 발생할 수 있다


*Resurrection

finalize메서드가 실행되는 동안 다시 Strongly reference가 실행되는 경우 발생할 수 있다

java doc에 따르면 Finalize는 한 객체에 대해 한번만 실행 되어야 한다고 되어있다

다른말로 여러번 실행하게 했을 경우에 대한 처리는 안되어 있다라고 해석 할수도있다

java doc의 명세에 안 맞는 구현은 이미 프로그램을 불안한 상태로 만든다고 볼수있고 잘못 수행되어도 개발자는 할말이 없게 된다

따라서 Resurrection상태는 피해야 한다


7.Deallocated

메모리의 반환이 끝난 상태로 GC의 동작이 마무리 된 상태




GC의 기준이 되는 것은 보통 Strongly reference의 여부이다

이 Strongly reference를 불필요하게 유지한다면 GC의 타겟이 되어야 할 객체들이 GC가 되지 않은채 메모리를 차지하게 된다

이런식으로 메모리가 증가하게되면 결국 사용가능한 메모리가 다 소진되어버린다

이때 두가지 경우가 발생하는데

하나는 가상메모리의 사용이다 가상메모리를 사용하게되면 성능저하가 발생한다

Ram과 Hard Disk사이의 memory swap은 생각보다 근 operation이다

또다른 하나는 OutofMemory이다

이녀석은 exception과 다르게 한번 넘어간다고 해결될 녀석이 아니다 이녀석이 발생했을 경우 메모리 해지가 제대로 일어나지 않는다면 계속 불리게 되고

error에 대한 예외처리를 하지 않았을 경우 어플리케이션이 다운될수있다

android의 경우는 메모리확보를 위해 중요도가 낮은 프로세스를 강제로 죽여버린다

미숙한 메모리 관리는 지속적인 GC로 이어지게되는데 GC자체가 매우 무거운 Operation이다 GC방식에 따라서 다른 동작들은 Hold하고 GC만을 수행할

경우도 있는데 이러면 GC가 끝날때까지 프로그램은 멈추게 되고  다른경우에는 프로그램성능이 하락되어 엄청 버벅거리게 된다

즉 다시말해 메모리 관리를 제대로 하지 못한것은 게임에 하나뿐인 유니크무기를가지고도 내구도가 0인채로 계속 사용하고 있는 거라 비유할수있다

GC를 강제로 부를 수도 있다

System.gc()나 Runtime.getRuntime().gc()를 통해서 할 수 있다 하지만 이 방법은 권장되지 않는다 이유는 GC가 소모하는 시스템 리소스 양이 많은데다

잘못된 프로그래밍은 버벅대기만 하는 프로그램을 만들 수도 있기 때문이다





댓글
댓글쓰기 폼
공지사항
최근에 달린 댓글
Total
13,549
Today
5
Yesterday
4
TAG
more
«   2022/06   »
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    
글 보관함