또자의 코딩교실

Garbage Collection(GC), Heap Memory의 개념 본문

코딩공부/java

Garbage Collection(GC), Heap Memory의 개념

또자자 2022. 3. 22. 23:36

Garbage Collection과 Heap Memory를 이해하기 위해서는

먼저 Java가 실행되는 전반적인 과정에 대한 이해가 필요합니다.


다음 그림은 자바 소스코드가 컴파일러를 거쳐 JVM을 통해 전체적으로 자바가 실행되는 과정을 나타낸 그림입니다.

 

출처 : https://hoonmaro.tistory.com/19

 

Java 소스코드는 사용자가 입력하는 명령어이자 POJO입니다. (ex. print('hello world!'); )
class 파일(소스코드+compiler(ex 컴퓨터언어의 파파고. 소스코드를 JVM이 해석가능한 바이트 코드로 변경해줌.)) 내엔 소스코드와 컴파일러가 포함되어 있습니다.

 

따라서 Class를 실행을 시켰다 가정해봅시다. 그러면 메모리를 할당받아 JVM이 작동하게 됩니다.

 

JVM이 메모리를 할당해서 컴퓨터 내에서 일을 수행하는 하나의 로봇이라 생각하였을 때,

(JVM은 물리적인 형태가 아닌 소프트웨어로서 하나의 개념으로 존재하기에 로봇이라 비유하였습니다.)
JVM로봇은 주입받은 class를 실행하고 컴파일러를 통해 번역된 바이트코드(소스코드 였던것)를 해석합니다.

 

JVM이라는 프로세스가 프로그램을 수행하기 위해 OS에서 할당받은 메모리 공간인 Runtime Data Area를 거쳐 JVM은 Thread 관리 및 Garbage Collection과 같은 메모리 정리 작업을 수행하고, 

미들웨어를 포함한 WAS를 통해 운영체제를 거쳐 최종적으로 하드웨어와 데이터베이스에 클라이언트가 원하는 동작을 수행합니다.

이전 JVM 내부에서 프로그램을 수행하기 위해 OS에서 할당받은 메모리 공간인 Runtime data areas 안의 구조를 좀 더 도식화 한다면 다음과 같습니다.

 

Runtime data areas 구조 도식화

<Thread1> 
- PC Register: CPU의 Register와 역할이 비슷하다.
  (CPU가 요청을 처리하는데 필요한 데이터를 일시적으로 저장하는 다목적 공간)
  (RAM과 다르다. Register는 프리미엄 단독 처리고, RAM은 먼 대중적 처리의 느낌.)
  현재 수행중인 JVM 명령의 주소값이 저장된다. (각 Thread별로 하나씩 생성)
 
- Stack Area: Method 내에서 사용되는 값들이 저장되는 구역
  (매개변수, 지역변수, 리턴값 등)
  메소드가 호출될때 LIFO로 하나씩 생성되고, 메소드 실행이 완료되면 LIFO로 하나씩 지워진다.
  (각 Thread별로 하나씩 생성)

 - Native Method Stack: 다른 언어(C/C++ 등) 의 메소드 호출을 위해 할당되는 구역
</Thread1> 

- Heap Area: new 명령어로 생성된 인스턴스와 객체가 저장되는 구역  (Garbage Collection 이슈는 이 영역에서 일어나며, 모든 Thread가 공유)

- Method Area : 클래스, 변수, Method, static변수, 상수 정보 등이 저장되는 영역(모든 Thread가 공유)

 

더보기

LIFO Stack은 먼저 넣은 것을 먼저 뺀다는 뜻으로 후입선출 방식을 뜻합니다. 

 

Runtime Data Area 중에 오늘 집중적으로 살펴볼 것은 Heap Area입니다. 

그만큼 Java는 Memory 이슈가 곧 프로그램의 성능이었고, 돈이었기에 이를 

메모리를 조금이라도 더 빠르게 하기위해 등장한 개념인 자동 Memory해제,

즉, Garbage Collection 이란 개념으로 발전 할 수 있었습니다.


[ Garbage Collection (가비지 컬렉션)이란? ]

프로그램을 개발 하다 보면 유효하지 않은 메모리인 가바지(Garbage)가 발생합니다.

C언어를 이용하면 free()라는 함수를 통해 직접 메모리를 해제해주어야 하지만

Java나 Kotlin을 이용해 개발을 하다 보면 개발자가 메모리를 직접 해제해주는 일이 없습니다.

 

그 이유는 JVM의 가비지 컬렉터가 불필요한 메모리를 알아서 정리해주기 때문입니다.

대신 Java에서 명시적으로 불필요한 데이터를 표현하기 위해서 일반적으로 null을 선언해준다.

예를 들어 아래와 같은 코드가 있다고 가정하겠습니다.

Blog blog_tistory = new Blog();
blog_tistory.setName("ddoza-1004");
blog_tistory = null; // 가비지 발생

// 이 때 변수 blog_tistory는 메모리에 할당된 공간이다. 
// 그리고 ddoza-1004는 blog_tistory 가 할당된 공간에 저장되는 값이다. 
// 이 값(ddoza-1004) 자체를 리터럴(소스 코드의 고정된 값을 대표하는 용어)이라고 한다.

Blog = new Blog();
Blog.setName("Zaev");

기존의 ddoza-1004 로 생성된 blog_tistory 객체는 더이상 참조를 하지 않고 사용이 되지 않아 (null 으로 선언)

Garbage(쓰이지 않는 비유효 메모리)가 되었습니다.

 

Java나 Kotlin에서는 이러한 메모리 누수를 방지하기 위해 가비지 컬렉터(Garbage Collector, GC)가 주기적으로 검사하여 메모리를 청소해준다. (물론 Java에서도 System.gc()를 이용해 호출할 수 있지만, 해당 메소드를 호출하는 것은 시스템의 성능에 매우 큰 영향을 미치므로 절대 호출해서는 안됨)


[ Heap Area와 그 구성요소들 (Java(JVM) Heap Memory Structure) ]

출처 :&nbsp;https://shinjekim.github.io/java/2020/01/06/%EC%9E%90%EB%B0%94%EC%9D%98-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0/

 

 

Heap Area는 Instance(Object)와 Array 객체 두 가지 종류만 저장되는 공간이며 모든 Thread가 공유하는 영역입니다.

힙 영역 = 모든 자바 클래스의 인스턴스(instance)와 배열(array)이 할당되는 곳,
              런타임(run time) 데이터를 저장하는 영역 

크기) Default 초기값은 64MB입니다.
-Xms VM option으로 지정됩니다.
XX:MaxPermSize VM option을 통해 변경될 수 있습니다.

힙 영역의 크기는 가비지 컬렉션의 전략에 따라 고정된 크기일수도 있고, 유동적으로 변경될 수도 있습니다.
JVM이 시작될 때 생성되어 애플리케이션이 실행되는 동안 프로그램이 잡아먹는 프로세스에 따라
크기가 유동적으로 커지거나 작아지며 변화하게 됩니다.

 

위에서 공부하셨다 시피, 많은 사람들이 Java의 메모리 구조는 곧 Heap이라 잘못 인식합니다.

그렇지만 각기 다른 정보를 저장할 뿐입니다. Instance(Object), Array 객체는 Heap Area에 저장이 되고, Thread 공유의 정보는 Stack에 저장이 되고 Class나 Method 정보, Bytecode 등은 Method Area에 저장되기 때문입니다.

 

저장 구역 Heap Area Stack Area Method Area
저장 정보 Instance(Object), Array Thread Class Variable, Method, ByteCode
기타 Thread 공유, GC발생 Thread별로 하나씩 생성, LIFO방식 Thread 공유

 

같은 애플리케이션을 사용하는 Thread 사이에서는

공유된 Heap Data를 이용할 때 동기화 이슈가 발생할 수 있습니다. 

Heap Data는 Thread를 공유하기 때문에 Stack에 저장이 된 정보가 같아지기 때문입니다.

 

원래 각 애플리케이션은 철저히 분리되어 서로에게 영향을 줄 수 없지만,

 

동일한 Instance를 공유하거나(동일한 Heap을 사용),

동일한 Class Variable을 사용하는 경우(동일한 Method Area를 사용),

모든 Thread들이 접근할 수 있기 때문에 동기화 문제가 수반됩니다.

(모든 Thread가 Heap을 공유하기 때문에 Method Area에서 충돌이 발생할 위험성이 있음)

 

JVM은 Java Heap에 Memory를 할당하는 Instruction(Bytecode로 new, newarray, anewarray, multianewarray)만

존재하고 메모리 해제를 위한 어떤 Java Code나 Bytecode도 존재하지 않습니다.

Java Heap의 메모리 해제는 오로지 Garbage Collection을 통해서만 수행됩니다.

 

현재 국내에서 많이 사용되는 WAS가 Tmax사의 JEUS, Oracle사의 WebLogic, IBM사의 WebSphere라고 볼때 WebSphere를 제외하고는 모두 Hotspot JVM(미국의 Longview Technologies LLC라는 회사에서 1999년에 처음 발표된 JVM)을 사용한다고 볼 수 있습니다. 물론 IBM AIX에서는 IBM JVM을 기본으로 제공하고 있습니다. Sun, Oracle, HP, Windows, Linux, MacOS에서 제공하는 JVM은 Hotspot JVM으로 명명하고 IBM에서 제공하는 JVM은 IBM JVM이라 부르기도 합니다.

 

출처 : https://hoonmaro.tistory.com/19

구조) 
Hotspot JVM의 Heap Area 구조를 기준으로 서술합니다. 크게 Young Generation과 Tenured Generation(Old Generation) 으로 나누어져 있습니다.

Minor GC = eden에 모인 array와 object들이 생과 사를 결정지어 Survivor 영역에서 숙성여부를 결정하는 것
Major GC = 오래 쓰인 Object와 array들이 최대 수용량을 넘어설 때 모두 소각되는 것
Perm 영역 = 보통 클래스의 메타 정보나 메소드의 메타데이터 저장영역이라고 불림.

Young Generation은 Eden 영역과 Survivor 영역으로 구성되는데 Eden 영역은 Object가 Heap에 최초로 할당되는 장소이며 Eden 영역이 꽉 차게 되면 Object의 참조 여부를 따져 만약 참조가 되어 있는 Live Object이면 Survivor 1 영역으로 넘기고, 참조가 끊어진 Garbage Object이면 그냥 Survivor 0에 남겨 놓습니다. 모든 Live Object가 Survivor 0과 1영역으로 넘어가면 Eden 영역을 모두 청소(Scavenge)합니다.

Survivor 영역은 말 그대로 Eden 영역에서 살아남은 Object들이 잠시 머무르는 곳입니다. 이 Survivor 영역은 두 개로 구성되는데(Survivor0, Survivor1) Live Object를 대피시킬 때는 하나의 Survivor 영역만 사용하게 됩니다. 이러한 전반의 과정을 Minor GC라고 합니다. 

Young Generation에서 Live Object로 오래 살아남아 성숙된 Object는 Old Generation으로 이동하게 됩니다. 여기서 성숙된 Object란 의미는 애플리케이션에서 특정 회수 이상 참조되어 기준 Age를 초과한 Object를 말합니다. Old Generation 영역은 새로 Heap에 할당되는 Object가 들어오는 것이 아니라, Generation 영역은 비교적 오랫동안 참조가 되어 이용되고 앞으로도 계속 사용될 확률이 높은 Object들을 저장하는 영역입니다. 이러한 Promotion 과정 중 Old Generation의 메모리도 충분하지 않으면 해당 영역에도 GC가 발생하는데 이를 가리켜 Full GC(Major GC)라고 합니다.

Perm 영역은 모통 Class의 Meta 정보나 Method의 Meta 정보, Static 변수와 상수 정보들이 저장되는 공간으로 흔히 메타데이터 저장 영역이라고도 합니다. 이 영역은 Java 8부터는 Native 영역으로 이동하여 Metaspace영역으로 변경되었습니다. (다만, 기존 Perm 영역에 존재하던 Static Object는 Hep 영역으로 옮겨져서 GC의 대상이 최대한 될 수 있도록 하였습니다)

Perm 영역 : 보통 Class의 Meta 정보나 Method의 Meta 정보, Static 변수와 상수 정보들이 저장되는 공간으로 메타데이터 저장 영역이라고 불리움. 하지만 Java 8부터는 Metaspace영역으로 바뀌어짐

+) Perm Area와 Stack Area의 차이점 : Thread 공유여부. 공유한다면 Stack Area이다.

 

 

Comments