본문 바로가기
Java/About Java

[Java] Java Memory

by seaweed_one 2022. 12. 28.
728x90

이번 포스팅에서는 자바의 메모리 영역에 대하여 알아보겠습니다.

본격적으로 알아보기에 앞서 왜 메모리 영역을 알아야 하는 걸까요?

 

자바에는 가비지 컬렉터(Garbage Collector)가 존재하여 사용하지 않는 메모리는 자동으로 회수해 줍니다.

개발자가 따로 메모리를 관리하지 않아도 손쉽게 프로그래밍을 진행할 수 있습니다.

하지만 메모리 설계가 잘 되어있는 프로그램과 그렇지 않은 프로그램은 속도나 안정성 면에서 차이가 날 것입니다.

메모리 구조를 이해하면 더욱 완성도 높은 프로그램 개발이 가능합니다.

 

그럼 본격적으로 메모리 구조를 알아보기 전에 먼저 자바 프로그램의 실행 절차에 대하여 알아보겠습니다.

JVM의 개념에 대해서 잘 모르시는 분들은 지난 포스팅을 읽어보시는 것을 추천합니다.

 

JDK, JVM, JRE

JDK는 JVM, JRE는 자바 프로그래밍에 사용되는 3대 핵심 기술 패키지입니다. 세 가지 기술의 개념과 연관성 그리고 차이점을 알아보려고 합니다. JVM JVM 이란? JVM 은 Java Virtural Machine의 약자입니다.

seaweed-one.tistory.com

자바 프로그램 실행 절차

  1. JVM은 OS로부터 메모리를 할당
  2. Source Code Compile (. java → .class)
  3. Byte Code로 변환된 파일을 JVM의 Class Loader로 보냄
  4. Class Loader를 통해 메모리에 저장 (JVM Runtime Data Area)
  5. Execution Engine을 통해 Runtime Data Area에 로딩된 Byte Code(.class 파일) 명령어 단위로 분류, 실행 및 Execution Engine에 의해 GC의 작동과 스레드 동기화

자바 프로그램 실행 과정

 

그럼 위의 실행 절차에서 각각의 구성요소들은 어떤 역할을 하는지 그리고 이번 포스팅에서 알아보고자 했던 Runtime Data Area에 대하여 알아보겠습니다.

JVM

Class Loader

자바는 동적으로 클래스를 읽어옵니다.

동적으로 클래스를 읽는다는 것은 어떤 의미일까요?

런타임이 되어야 모든 코드가 JVM과 연결된다는 뜻으로 이 역할을 수행하는 것이 바로 class loader입니다.

클래스 로더는 .class 파일을 묶어 Runtime Data Area로 적재합니다.

Execution Engine

실행 엔진은 로드된 바이트코드를 실행하는 런타임 모듈입니다.

이렇게만 이야기하면 이해하기가 쉽지 않습니다.

풀어서 이야기하자면 클래스 로더에 의해 JVM으로 로드된 .class 파일들은 Runtime Data Areas의 Method Area에 배치됩니다.

JVM은 Method Area의 바이트 코드를 실행 엔진에 제공하여 코드를 실행시킵니다.

초기에는 인터프리터 방식이었기 때문에 속도가 느리다는 단점이 있었으나 JIT(just-in-time compilation) 컴파일러 방식으로 프로그램을 실제 실행 시점에 기계어로 번역하는 컴파일 기법을 통해 이 점을 보완하였습니다.

하지만 JIT도 단점은 존재합니다. 바이트 코드를 네이티브 코드로 바꿔 실행은 빠르지만 변환하는데 비용이 발생한다는 것인데요.

하여 JVM은 인터프리터 방식을 사용하다가 일정한 기준이 넘어가면 JIT 컴파일러 방식으로 실행합니다.

Garbage Collector

줄여서 GC라고도 부릅니다.

JVM은 GC를 이용하여 더는 사용하지 않거나 null 인 객체의 메모리를 해제하여 자동으로 회수해 줍니다.

하여 위에서 말씀드린 것처럼 개발자가 따로 메모리를 관리하지 않아도 Heap 메모리 영역에 생성된 객체들 중 참조되지 않은 객체들을 탐색 후 제거 할 수 있습니다.

GC의 실행 시간은 정확히 알 수 없으며 실행 시 GC 역할을 수행하는 스레드를 제외한 나머지 모든 스레드들은 일시정지상태가 됩니다.

Runtime Data Area

JVM이 프로그램을 수행하기 위해 운영체제로부터 할당받은 메모리 공간으로 애플리케이션을 실행 시 사용되는 데이터들을 적재하는 영역입니다. 아래와 같은 영역이 포함됩니다.

  • 힙 영역 (Heap Area)
  • 메서드 영역(Method Area)
  • 스택 영역(Stack Area)
  • PC 레지스터 (PC Register)
  • 네이티브 메서드 스택(Native Method Stack)

Java Runtime Data Area

그림과 같이 Heap Area, Method Area는 모든 스레드가 공유하여 사용하는 영역으로 GC의 대상이 됩니다.

두 영역 외에는 각 스레드마다 하나씩 생성되는 영역으로 보시면 됩니다.

그럼 각 영역에 대하여 자세하게 알아볼까요?

Method Area

JVM이 실행되면서 생성되는 공간으로 모든 스레드에서 정보가 공유되는 영역입니다.

각종 필드 정보 (멤버 변수명, 데이터 타입, 접근제어자 등)와 메서드 정보, 데이터 타입 등의 정보가 저장됩니다.

Runtime Constant Pool 은 이름에서 알 수 있듯이 상수 정보가 저장됩니다.

Heap

동적으로 생성된 데이터 (new 키워드로 생성된 객체와 배열)가 저장되는 공간으로 참조 타입의 데이터가 저장되며 모든 스레드에서 정보가 공유됩니다.

 

Heap에 저장된 데이터는 GC가 처리하지 않는 한 삭제되지 않습니다. 하여 GC는 주기적으로 불필요 객체를 제거합니다.

힙 영역은 아래와 같이 크게 Young Generation, Tenured Generation, Permanent Generation 세 가지 영역으로 나뉩니다.

Java Heap Memory

각 영역에 대하여 조금 더 알아보겠습니다.

Young Generation

new를 이용하여 객체 생성 시 저장되는 영역입니다. Eden 영역에 최초로 할당되고 해당 영역에 데이터가 쌓이게 되면 참조정도에 따라 Servivor로 이동되거나 제거되며 Minor GC 수행됩니다.

Old(Tenured) Generation

Young Generation 영역에 있는 객체가 오래되면 저장되는 공간으로 이 영역에서 Major GC(Full GC)와 Minor GC 가 수행됩니다.

Permanent Generation

동적으로 클래스가 로딩되는 경우에 사용되는 영역으로 클래스 로더에 의해 로드되는 클래스, 메서드 등에 대한 메타 정보가 저장됩니다.

 

위에서 잠시 등장한 Minor GC 그리고 Major GC 란 무엇일까요?

각 영역은 효율적은 GC 수행을 위하여 나누어져 있다고도 볼 수 있는데 Heap 영역에서 위에서 잠시 등장한 Minor GC, Major GC와도 연관이 있습니다.

두 가지 종류의 GC 에 대하여 간단하게 알아보겠습니다.

Minor GC
Young Generation에서 객체 생성시 Eden 영역에 최초로 할당됩니다.
해당 영역에 데이터가 쌓이게 되면 참조정도에 따라 Servivor로 이동되거나 제거됩니다.

Young Generation 영역이 가득차게되면 또 참조정도에 따라 Old영역으로 이동 되게 되거나 제거됩니다.
Young Generation Tenured Generation 영역에서 위와 같은 동작을 수행하는 GC를 Minor GC 라고 합니다.

Major GC
Old영역의 메모리가 가득 차게되면 해당 영역의 모든 객체들을 검사합니다.
참조되지 않는 객체들을 한번에 제거하는데 이 때 실행되는 GC가 바로 Major GC입니다.
Major GC의 작업은 시간이 오래 걸리며 GC 실행 스레드를 제외한 모든 스레드는 작업을 멈춥니다.
이를 'Stop-the-World' 라 합니다.

 

Stack

스레드별로 존재하는 영역입니다.

메서드 호출 시마다 개별적으로 스택이 생성되며 생성되는 스레드의 수행정보를 프레임에 저장합니다.

스택 영역은 그 메서드만을 위한 공간으로 메서드 안에서 사용되는 값들을 저장하는데 예를 들어 지역변수, 메서드의 매개변수, 리턴 값 및 연산으로 생성된 임시값과 같이 잠시 사용되고 필요가 없어지는 데이터들이 있습니다.

 

지역변수임에도 참조타입인 객체의 경우 Heap에 저장된 데이터의 주소값을 Stack에 저장하여 사용합니다.

잘 이해가 안 가시죠?

예를 들어 Seaweed sw = new Seaweed(); 라는 소스코드 작성 시 Seaweed sw는 스택 영역에, new로 생성된 Seaweed 클래스의 인스턴스는 Heap 영역에 생성됩니다.

그리고 스택 영역에 생성된 sw는 힙 영역의 주소값을 값으로 가지고 있습니다.

스택 영역에 생성된 객체가 힙 영역에 생성된 객체를 참조하고 있는 것입니다.

이 영역은 LIFO(선입후출)의 구조로 나중에 들어온 데이터가 먼저 나가게 되며 스택 영역은 메서드 수행이 끝나면 프레임별로 삭제됩니다.

 

PC Register

JVM이 실행하고 있는 현재 위치의 명령어와 주소값을(Program Counter)를 저장하는 역할을 담당하며 스레드 생성 시 생성됩니다.

 

Native Method Stack

네이티브 코드(자바 외의 C, C++로 작성된 코드) 실행 시 사용됩니다.

 

728x90

'Java > About Java' 카테고리의 다른 글

[Java] 생성자  (2) 2023.01.31
[Java] Java Memory Leak  (0) 2023.01.11
[Java] Primitive Type & Reference Type  (0) 2023.01.09
[Java] JNA? JNI?  (0) 2023.01.04
[Java] JDK, JVM, JRE  (0) 2022.12.27