티스토리 뷰

728x90

본 게시물은 해당 블로그에서 레퍼런스를 번역한 자료임을 알립니다.

퍼가실때 해당 블로그의 주소는 밝히실 필요가 없으나 레퍼런스의 주소는 밝혀주시면 감사하겠습니다.

 

자바의 GC를 이용한 메모리 관리는 해당 언어가 이륙한 엄청난 성과 중 하나라 할 수 있다. GC는 개발자로 하여금 메모리 할당과 할당 해제를 걱정할 필요없이 새로운 객체를 생성할 수 있게 해주었는데, 이는 GC가 자동으로 메모리를 다시 회수해주기 때문이다. 메모리 누수와 다른 메모리 관련된 문제를 전혀 걱정할 필요없이 더 적은 보일러플레이트 코드를 작성하게 함으로써 더 빠른 개발을 가능하게 하였다. (이론적으로는)

 

아이러니하게도 자바의 GC가 일을 "너무" 잘 하는 바람에, 너무 많은 객체를 생성하고 제거한다. 덕분에 대부분의 메모리 관련 문제는 해결되었지만 심각한 성능 저하라는 대가를 종종 치루곤 한다. GC가 어떤 상황에서든 적용 가능하도록 만드는 것은 복잡하며 최적화하기 쉽지 않은 시스템을 만들게 되었다. GC를 공부하기 이전에, JVM에서 메모리 관리가 어떻게 동작하는지 먼저 이해할 필요가 있다.

 

자바의 GC는 어떻게 동작할까

많은 사람들은 GC가 죽은 객체를 찾고 내다버리는 것이라고 생각하곤 한다. 하지만 실제로는 자바의 GC는 이와 정반대로 동작한다! 살아있는 객체를 추적하며 이외의 모든 것은 가비지라고 판단하는 것이다. 이런 기본 원리에 대한 잘못된 이해는 성능 이슈를 야기시킬 수 있다.

 

메모리의 동적 할당을 위해 사용되는 영역인 힙을 먼저 살펴보자. 대부분의 경우 프로그램이 동작하기 위한 힙 영역이 OS에 의해 할당되고 JVM이 이를 관리하게 된다. 이는 두 가지 중요한 결과로 이어지게 되는데,

 

1. 객체 생성이 빨라진다. 모든 객체 하나 하나에 OS를 통한 전역 동기화(global synchronization)를 할 필요가 없어지기 때문이다. 할당 과정이 단순히 메모리 배열의 일부분을 요청하고 메모리의 오프셋 포인터를 앞으로 이동시키기만 하는 것으로 끝난다 (Figure 2.1 참고). 이 다음 할당은 이동된 오프셋을 기준으로 시작해서 요청하는 양만큼 할당되게 된다.

 

2. 객체가 더 이상 사용되지 않으면 GC는 객체가 사용하던 메모리를 다시 회수해서 앞으로 사용하게 될 객체의 메모리 할당을 위해 사용한다. 이 말은 즉 메모리 공간을 더 이상 이용하지 않는다고 해서 OS에게 다시 되돌려준다는 소리가 아니라는 뜻이다.

 

Figure 2.1: 새로운 객체들은 단순히 힙 영역의 마지막에 할당된다.

 

힙 영역에 할당되는 모든 객체들은 JVM에 의해 관리된다. 개발자가 사용하는 모든 것들(코드 그 자체부터 시작해서 스태틱 변수, 클래스 객체들 등)을 포함해서 말이다. 객체가 참조되고 있는 동안에는 JVM은 이를 살아있는 상태라고 간주한다. 애플리케이션의 코드에 의해 객체가 더 이상 참조되지 않는 상태가 되면 GC는 이를 제거하고 메모리를 다시 회수한다. 말로는 쉬워보이지만 이쯤에서 한 가지 의문이 생긴다. 참조 과정에서 첫 번째로 참조되는 것이 무엇인지 어떻게 알 수 있을까?

 

GC 루트(Roots) - 모든 객체 구조의 근원

모든 객체는 반드시 하나 이상의 루트 객체를 가지게 된다. 애플리케이션에서 이 루트 객체를 참조할 수 있다면 루트 객체에 딸린 모든 객체들 또한 참조 가능하다는 얘기이다. 그렇다면 루트 객체의 어떤 상태가 참조 가능한 상태인걸까? GC 루트(Figure 2.2 참고)라 불리는 특별한 객체가 존재하는데 이 객체는 언제나 참조 가능한 상태이고 어떤 객체든 이 GC 루트를 최상위 루트로 가지게 된다.

 

자바에는 다음과 같은 4가지의 GC 루트가 존재한다.

1. 지역 변수는 스레드의 스택 영역에 존재한다. 이는 진짜로 존재하는 객체 참조가 아니므로 가시화된 상태가 아니다(This is not a real object virtual reference and thus is not visible). 이는 지역 변수가 GC 루트라는 의미이다.

2. 활성화된 자바 스레드는 언제나 살아있는 상태로 간주되기에 GC 루트라 할 수 있다. 이는 스레드 지역 변수에 있어 중요한 요소이다.

3. 스태틱 변수는 본인 클래스에 의해 참조된다. 이 사실이 스태틱 변수를 사실상(de facto) GC 루트로 만든다. 하지만 스태틱 변수의 클래스 자체는 GC에 의해 제거될 수 있고 이는 모든 참조되는 스태틱 변수를 제거하는 결과로 이어진다. 이는 우리가 애플리케이션 서버를 다룰때 특히 더 중요한데, OGSi 컨테이너나 클래스 로더가 이에 속한다. 이에 대해서는 문제 패턴 섹션에 더 자세히 다룰 것이다.

4. JNI 참조는 JNI 호출에 의해 생성되는 네이티브 코드를 위한 자바 객체이다. 이 객체는 JVM 입장에서 네이티브 코드에 의해 참조되고 있는지 아닌지 알 수 없기에 특별히 다뤄진다. 이런 객체는 매우 특별한 형태의 GC 루트를 대표하는데 이에 대해서는 문제 패턴 섹션에 더 자세히 다룰 것이다.

 

Figure 2.2: GC 루트는 JVM 자체에 의해 참조되는 객체이므로 이를 통해 GC를 할 수 있는 것이다.

그러므로 일반적인 자바 애플리케이션은 다음과 같은 GC 루트를 가지고 있다.

  • main 메서드의 지역 변수
  • main 스레드
  • main 클래스의 스태틱 변수

Mark and Sweep

어떤 객체가 더 이상 참조되고 있는지 알기 위해서 JVM에서는  Mark and Sweep 알고리즘을 비규칙적으로 실행한다. 이름에서부터 알 수 있듯이 2가지 과정으로 이루어지는데,

 

1. GC 루트에서 시작해서 모든 객체 참조를 순회하며 이때 마크되는 모든 객체는 살아있는 것으로 간주한다.

2. 마크된 객체에 의해 할당되지 않은 모든 힙 영역의 메모리는 회수된다. 단순히 사용되지 않는 객체를 사용할 수 있다고 마크하는 것으로 끝난다. 

 

GC는 '참조되고 있지 않지만 삭제되지 않은 객체의 메모리' 즉 메모리 누수의 근본적인 발생 원인이 발생하지 않도록 설계되었다. 하지만 개발자가 단순히 참조 해제하는 것을 까먹는 것만으로 사용되지 않는 객체이지만 참조는 되고 있는 경우가 있을 수 있으므로 이는 언제나 통하는 것은 아니다. 이러한 객체는 GC에 의해 제거될 수 없다. 심지어는 이런 논리적인 메모리 누수는 어떤 방법으로도 감지할 수 없다 (Figure 2.3 참고). 현존하는 가장 좋은 분석 도구도 의심스러운 객체를 알려줄 수 있을 정도일뿐이다. 우리는 메모리 누수 분석에 대해서는 밑에서 자세히 다룰 것이다.

 

Figure 2.3: GC 루트에 의해 직간접적으로 참조될 수 없는 객체는 제거될 것이다. 하지만 논리적인 실수에 의한 참조 문제는 직접 제거하지 못 하고 개발자에게 알려주는 정도이다.

 

레퍼런스

https://www.dynatrace.com/resources/ebooks/javabook/how-garbage-collection-works/

 

eBook: How Java Garbage Collection Works | Dynatrace

Download our Java memory management ebook chapter about Java Garbage Collection for free!

www.dynatrace.com

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
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
글 보관함