본문 바로가기
언어/java

[java] JVM 동작 방식 및 메모리 구조

by 개발LOG 2024. 12. 23.
반응형

일반적으로 대부분의 컴파일러는 컴파일과정을 통해 기계어로 작성된 실행 프로그램을 생성한다. 하지만 JAVA는 이와 달리 OS와 독립적인 특징으로 인해, 컴파일 과정에서 기계어가 아닌 바이트 코드로 작성된 .class 파일을 생성한다.

 

그렇다면 기계어로 번역하는 역할은 자바에서 누가 할까? 그것이 바로 JVM(Java Virtual Machine)의 역할이다.

JVM은 OS와 프로그램 사이에서 컴파일과정을 통해 생성된 바이트코드(.java -> .class)를 기계어로 번역해주는 역할을 한다. 이 자세한 과정에 대해 알아보자.

자바 컴파일/런타임 환경 흐름도

JVM 동작 방식

  1. 개발자가 자바 소스코드(.java)를 작성한다.
  2. Build 라는 작업을 통해 자바 컴파일러의 javac 라는 명령어로 바이트코드인 .class 파일을 생성한다.
  3. 컴파일된 바이트 코드를 JVM의 클래스로더(Class Loader)에게 전달합니다.
  4. 클래스 로더는 동적로딩(Dynamic Loading)을 통해 필요한 클래스들을 로딩 및 링크하여 런타임 데이터 영역(Runtime Data area), 즉 JVM의 메모리에 올립니다.
  5. 실행엔진 Execution Engine (인터프리터와 JIT컴파일러)은 JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와서 실행한다.
    1. 인터프리터 : 바이트 코드 명령어를 하나씩 읽어서 해석하고 실행한다. 하나하나의 실행은 빠르나, 전체적인 실행 속도가 느리다는 단점을 가진다.
    2. JIT 컴파일러(Just-In-Time Compiler) : 인터프리터의 단점을 보완하기 위해 도입된 방식으로 바이트 코드 전체를 컴파일하여 Native Code (네이티브 코드란, JAVA에서 부모가 되는 C언어나, C++, 어셈블리어로 구성된 코드를 의미한다.)로 변경하고 이후에는 해당 메서드를 더 이상 인터프리팅 하지 않고 캐싱해 두었다가 네이티브 코드로 직접 실행하는 방식이다.
      하나씩 인터프리팅하여 실행하는것이 아니라, 컴파일된 네이티브 코드를 실행하는 것이기 때문에 전체적인 실행 속도는 인터프리팅 방식보다 빠르다.하지만 바이트코드를 Native Code로 변환하는 데에도 비용이 소요되므로, JVM은 모든 코드를 JIT 컴파일러 방식으로 실행하지 않고 인터프리터 방식을 사용하다 일정 기준이 넘어가면 JIT 컴파일 방식으로 명령어를 실행하는 식으로 진행한다.
  6. 이 과정에서 Execution Engine에 의해 Garbage Collector의 작동과 Thread 동기화가 이루어진다.

 

가비지 컬렉터 (Garbage Collector ,GC)

 

 

자바 가상 머신은 가비지 컬렉터(garbage collector)를 이용하여 Heap 메모리 영역에서 더는 사용하지 않는 메모리를 자동으로 회수해 준다.

C언어 같은 경우 직접 개발자가 메모리를 해제해줘야 되지만, JAVA는 이 가비지 컬렉터를 이용해 자동으로 메모리를 실시간 최적화 시켜준다. 따라서 개발자가 따로 메모리를 관리하지 않아도 되므로, 더욱 손쉽게 프로그래밍을 할 수 있도록 해준다.

일반적으로 자동으로 실행되지만, 단 GC(가비지 컬렉터) 가 실행되는 시간은 정해져 있지 않다.

특히 Full GC 가 발생하는 경우, GC 를 제외한 모든 스레드가 중지되기 때문에 장애가 발생할 수 있다.


 JVM 런타임 데이터 영역(Runtime Data Areas) 메모리 구조

 

Runtime Data Areas

 

 

 

Method(Class, Static) Area

메서드 영역은 JVM이 시작될 때 생성되는 공간으로 바이트 코드(.class)를 처음 메모리 공간에 올릴 때 초기화되는 대상을 저장하기 위한 메모리 공간이다. JVM이 동작하고 클래스가 로드될 때 적재되서 프로그램이 종료될 때까지 저장 된다.

모든 쓰레드가 공유하는 영역이라 다음과 같이 초기화 코드 정보들이 저장되게 된다.

-Field Info : 멤버 변수의 이름, 데이터 타입, 접근 제어자의 정보
-Method Info : 메소드 이름, return 타입, 함수 매개변수, 접근 제어자의 정보
-Type Info : Class 인지 Interface 인지 여부 저장, Type의 속성, 이름 Super Class의 이름

즉, 클래스와 메소드, 멤버(클래스, 인스턴스)변수와 상수(final) 정보 등이 저장되는 공간이라 할 수 있다.

 

 

Heap Area

New명령어를 통해 생성한 인스턴스와 배열 등의 참조형 변수정보가 저장되는 공간이다. 물론 Method Area에 올라온 클래스들만 생성이 가능하다. GC의 대상이 된다.

힙영역

 

유의할점은 힙 영역에 생성된 객체와 배열은 Reference Type으로서, JVM 스택 영역의 변수나 다른 객체의 필드에서 참조된다는 점이다.
즉, 힙의 참조 주소는 "스택"이 갖고 있고 해당 객체를 통해서만 힙 영역에 있는 인스턴스를 핸들링할 수 있는 것이다.

만일 참조하는 변수나 필드가 없다면 의미 없는 객체가 되기 때문에 이것을 쓰레기로 취급하고 JVM은 쓰레기 수집기인 Garbage Collector를 실행시켜 쓰레기 객체를 힙 영역에서 자동으로 제거된다.
 
이처럼 힙 영역은 가비지 컬렉션에 대상이 되는 공간이다.

 

 

Stack Area

스택 영역은 int, long, boolean 등 기본 자료형을 생성할 때 저장하는 공간으로, 임시적으로 사용되는 변수나 정보들이 저장되는 영역이다.

 

스택 자료구조 LIFO

 

자료구조 Stack은 마지막에 들어온 값이 먼저 나가는 LIFO  구조로 push와 pop 기능 사용방식으로 동작한다.
 
메서드 호출 시마다 각각의 스택 프레임(그 메서드만을 위한 공간)이 생성되고 메서드 안에서 사용되는 값들을 저장하고, 호출된 메서드의 매개변수, 지역변수, 리턴 값 및 연산 시 일어나는 값들을 임시로 저장한다.
그리고 메서드 수행이 끝나면 프레임별로 삭제된다.

 

단, 데이터의 타입에 따라 스택(stack) 과 힙(haeap)에 저장되는 방식이 다르다는 점은 유의해야 한다.

  • 기본(원시)타입 변수는 스택 영역에 직접 값을 가진다.
  • 참조타입 변수는 힙 영역이나 메소드 영역의 객체 주소를 가진다.

 

스택영역과 힙영역 차이

 

예를들어 Person p = new Person(); 와 같이 클래스를 생성할 경우, new 에 의해 생성된 클래스는 Heap Area 에 저장되고, Stack Area 에는 생성된 클래스의 참조인 p 만 저장된다.

스택 영역은 각 스레드마다 하나씩 존재하며, 스레드가 시작될 때 할당된다.

프로세스가 메모리에 로드 될 때 스택 사이즈가 고정되어 있어, 런타임 시에 스택 사이즈를 바꿀 수는 없다.

만일 고정된 크기의 JVM 스택에서 프로그램 실행 중 메모리 크기가 충분하지 않다면 StackOverFlowError가 발생하게 된다.

쓰레드를 종료하면 런타임 스택도 사라진다.

 

여기까지 배운 메소드 영역, 힙 영역, 스레드 영역을 한 그림으로 표시하자면 다음과 같이 도식이 그려지게 된다.

 

JVM 메모리 구조



 

 

 

PC Register Area

PC 레지스터는 쓰레드가 시작될 때 생성되며, 현재 수행중인 JVM 명령어 주소를 저장하는 공간이다.
JVM 명령의 주소는 쓰레드가 어떤 부분을 무슨 명령으로 실행해야할 지에 대한 기록을 가지고 있다.
 
일반적으로 프로그램의 실행은 CPU에서 명령어(Instruction)를 수행하는 과정으로 이루어진다.
이때 CPU는 연산을 수행하는 동안 필요한 정보를 레지스터라고 하는 CPU 내의 기억장치를 이용하게 된다.

 

하지만 자바의 PC Registercpu Register와 다르다.
자바는 OS나 CPU의 입장에서는 하나의 프로세스이기 때문에 가상 머신(JVM)의 리소스를 이용해야 한다.
그래서 자바는 CPU에 직접 연산을 수행하도록 하는 것이 아닌, 현재 작업하는 내용을 CPU에게 연산으로 제공해야 하며, 이를 위한 버퍼 공간으로 PC Register라는 메모리 영역을 만들게 된 것이다
따라서 JVM은 스택에서 비연산값 Operand를 뽑아 별도의 메모리 공간인 PC Register에 저장하는 방식을 취한다.

 

만약에 스레드가 자바 메소드를 수행하고 있으면 JVM 명령(Instruction)의 주소를 PC Register에 저장한다.
그러다 만약 자바가 아닌 다른 언어(C언어, 어셈블리)의 메소드를 수행하고 있다면, undefined 상태가 된다.
왜냐하면 자바에서는 이 두 경우를 따로 처리하기 때문이다.
이 부분이 바로 뒤에 언급하게 될 Native Method Stack 공간이다.

 

Native Method Stack Area

네이티브 메서드 스택은 자바 외 다른 언어의 호출을 위해 할당되는 영역이다. 자바에서 C/C++의 메소드를 호출할 때 사용하는 Stack 영역이라고 생각하시면 된다.

 

예를 들어 getList, insertList, updateList, deleteList CRUD 메소드가 있는 ListController class가 있을 때 이 클래스와 메소드의 정보는 실행엔진에 의해 Method 영역에 올라가며, 클래스의 메소드 호출이 발생하면 Method 영역의 정보를 읽어 해당 메소드의 매개변수, 지역변수 리턴값 등이 Stack 영역에 올려 처리되게 됩니다. 그리고 메소드의 실행이 끝나면 Stack 영역에서는 자동으로 제거되게 된다.

 

만약 메소드 내에 New 명령어로 생성한 인스턴스나 배열이 있을 경우 해당 값은 Heap 영역에 저장되고 Stack 영역에서는 이 Heap 영역의 값을 참조 할 수 있는 메모리 주소 값만 저장되게 됩니다.

배열을 System.out.println(); 하게 되면 메모리 주소 값이 출력되는 이유가 이것이다.

 

 

 

 


참고링크:

https://gyoogle.dev/blog/computer-language/Java/%EC%BB%B4%ED%8C%8C%EC%9D%BC%20%EA%B3%BC%EC%A0%95.html

 

[Java] 컴파일 과정 | 👨🏻‍💻 Tech Interview

[Java] 컴파일 과정 들어가기전 자바는 OS에 독립적인 특징을 가지고 있다. 그게 가능한 이유는 JVM(Java Vitual Machine) 덕분이다. 그렇다면 JVM(Java Vitual Machine)의 어떠한 기능 때문에, OS에 독립적으로

gyoogle.dev

 

https://aljjabaegi.tistory.com/387

 

알기쉽게 정리한 JAVA의 컴파일과정 및 JVM 메모리 구조, JVM GC

알기쉽게 정리한 JAVA의 컴파일과정 및 JVM 메모리 구조, JVM GC 자바 개발자들이 간과 하기 쉬운 JAVA의 메모리 구조에 대해 포스팅 해보려고 합니다. 이와 관련하여 JAVA의 컴파일 과정과 Garbage Collect

aljjabaegi.tistory.com

 

https://inpa.tistory.com/entry/JAVA-%E2%98%95-JVM-%EB%82%B4%EB%B6%80-%EA%B5%AC%EC%A1%B0-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%98%81%EC%97%AD-%EC%8B%AC%ED%99%94%ED%8E%B8

 

☕ JVM 내부 구조 & 메모리 영역 💯 총정리

저번 포스팅에서는 JRE / JDK / JVM에 대해서 간략하게 알아보는 시간을 가졌다면, 이번 포스팅에서는 JVM의 내부 구조에 대해 좀 더 자세하게 알아보도록 할 예정이다. JVM(자바 가상 머신)은 자바 언

inpa.tistory.com

 

 

반응형

'언어 > java' 카테고리의 다른 글

[java] 삼항연산자  (0) 2025.04.09
[java] 접근제어자  (1) 2024.07.23
[java] HashMap  (0) 2024.07.22
[java] 제네릭스, ArrayList  (0) 2024.07.22
[java] StringBuffer, StringBuilder, String 차이  (0) 2024.07.22