본문 바로가기
질문을 해결하는 과정

[Java] JVM의 메모리 구조와 GC에서 살아남는 법

by 무자비한 낭만주먹 2024. 1. 7.

[그림1]. 오늘도 감사한 공부 시작 ~

목차
0. 개요
1. JVM은 뭐에요?
2. C, C++ .. 중간에 Virtual Machine 왜 안써?
3. 프로세스로서의 JVM
4. JVM에서 일어나는 대략적인 사건들
5. 늙었다는 건 .. 살아남았다는 것.

 

 

0. 개요
a. JVM의 메모리 구조
'JVM의 메모리 구조에 대해서 설명해보세요'


나는 위 질문을 듣고 아무 생각없이 "어 .. method, stack, heap 등의 영역이 있고.. 각 영역은 .." 같은 기계적인 답변을 늘어놨다. 근데 그 와중에 머리를 스치는 질문이 생겼는데 '아니 JVM은 프로세슨데 뭔 [메모리 영역] 같은 소리를 하는거지? virtual machine이라고 불러주니까 지가 진짜 머신인줄 아는건가? 그럼 내부 메모리 영역은 가상으로 할당한 영역을 의미하는건가? 가상화는 누가 한거야?

[그림2]. 최찬혁은 참지않아.
이걸 지금 봐도 되나 ..


사실 공부할게 산더미라 저 생각을 정리하면 뭔가 확실히 문제가 생길거지만 일단 그건 미래의 내가 알아서 할 일이고 지금은 본능을 쫓아 공부해보기로 했다.

 

1. JVM은 뭐에요?
a. JVM은 ..
그냥 가상의 machine이다.


JavaVirtualMachine이니까 그냥 이름 그대로 이해하면 되는데 여기서 중요한건 '가상'이라는 표현이다. 가상이란게 결국 실제 기계가 아니라 프로그램으로 machine을 표현했다는건데 왜 표현했을까?

b. JVM의 존재 이유
Java 만의 특별한 컴파일 레시피 ~ ✨

 

[그림3]. JAVA는 다르다.


자바, 좀 더 정확히 표현하자면 JDK는 컴파일을 통해 기계어를 직접 컴파일해내지 않고 JVM이라는 가상 컴퓨터를 경유한다. 기존에는 컴퓨터(CPU)가 이해할 수 있는 언어도 컴퓨팅 환경마다 다르기 때문에 '한 번 컴파일 한 코드'를 모든 환경에서 사용할 수 없었다. 

어쨌든 지금은 JVM은 '운영체제와 언어에 독립적으로 개발할 수 있는 걸 돕기 위해 만들어진 '중간 통역사 프로그램' 정도로 이해하면 된다.

 

2. C, C++ .. 중간에 Virtual Machine 왜 안써?
a. 걔들은 안쓰는 이유 ..
[그림4]. 느려져 ..

애석하게도 JVM을 이용하면 '무조건'은 아니지만 때때로 더 낮은 성능을 가져가게 될 수 있다. 이마저도 '목적에 따른 메모리 영역의 분리'라던지 'GC'같은 것들을 이용해 최적화를 했을 때의 이야기다. 그래서 걔들(C, C++ 등..)은 이런 복잡성 때문에 Virtual Machine을 안쓴다.



b. 잠깐 .. 메모리 영역의 분리?
그렇다. JVM은 안정성, 효율성을 위해 내부적으로 '가상의 메모리 공간'을 사용하고 이를 분리한다. 일단 말로 설명하면 이해하기 어려우니 그림을 보자.



 

3. 프로세스로서의 JVM
a. JVM도 결국엔 프로세스
[그림5]. 프로세스로서의 JVM


JVM은 결국 프로그램이고 메모리에 올라갔을 때 '운영체제로 부터 메모리 영역을 할당' 받는다. 정말 실제 메모리 영역을 할당받는다는 의미이다. 그리고 JVM이 가지고 있는 Heap이나 Stack 같은 메모리 영역들은 사실 JVM이 할당받은 메모리 영역을 가상으로 구분해 관리하는 것이다.

OS로서의 메모리와 JVM이 다루는 가상 메모리를 구분하지 않고 설명하는 놈들은
애초에 가르칠 생각이 없거나 JVM이 뭔지 모르는 놈들이다.

 

b. 가상의 메모리 영역
결국 JVM의 메모리 라는거는 'JVM이 운영체제로 부터 할당받은 실제 메모리 영역을 지가 실제 기계인냥 자체적으로 구분한 것을 말한다.'

 

 

4. JVM에서 일어나는 대략적인 사건들
a. 컴파일 과정
[그림6]. 컴파일 과정에서의 JVM


컴파일 과정에선 JVM은 무슨 일을 할까? 우리가 jdk를 이용해 우리의 말(자연 코드)로 작성된 코드를 컴파일 하면 JVM(javaVitualMachine)이 이해할 수 있는 언어인 JavaByteCode로 컴파일된다. 이게 첫번째 컴파일이다.

이제 자바 바이트코드를 JRE의 java run을 이용해 실행하면 클래스 로더가 뜨면서 정적 정보들(클래스 정보, 상수, 정적 변수 등..)이 method 영역에 로드하고 인터프리터를 통해 코드를 한줄 씩 통역하면서 CPU와 협력해 메인 쓰레드의 작업을 수행하는데 이 때 반복적으로 많이 쓰여지는 녀석들을 hotspot이라는 곳에 모아둔 뒤 JIT 라는 녀석을 통해 미리 '이진코드'로 컴파일해둔다. 이게 두 번째 컴파일이다.

b. 동적 데이터가 다뤄지는 과정
[그림7]. 동적 데이터가 다뤄지는 과정

이제 Interpter를 통해 '애플리케이션 스레드'의 코드들을 한줄씩 이진 코드로 실시간 번역하는데 이 과정에서 발생하는 '객체' 정보의 경우 Heap 영역에 저장된다. Heap 영역에 저장된 순간 JVM의 시스템 쓰레드에서 돌아가고 있는 GC의 관리 안으로 들어오는데 이 GC라는 녀석은 해당 정보를 Eden이라고 하는 영역으로 보내면서 GC의 관리가 시작된다.


[Process]
1. Eden 영역이 가득 차거나 프로그래머의 신호나 메모리 경고 신호등이 일어날 때 MinorGC 라는게 일어나는데 이 때 생존하는 객체들은 Survivor 영역으로 보낸다. 
2. 저 MinorGC가 일어날 때 Survivor1, 2 영역도 정리가 진행되는데 Survivor1, 2영역을 번갈아가며 mark and sweep 알고리즘으로 청소한다. (그냥 안쓰는 객체 제거하고 반대편 Survivor로 순차적으로 채운다는 뜻)
3. 저렇게 survivor 영역을 옮겨 다닐 때 마다 '세대 카운트(Genration Count, Age Count ..)라는 걸 체크하는데 이게 사전에 정의해둔 생존 임계값을 넘어서게 되면 Old Generation으로 이동하게 된다.
4. 저 Old Generation 영역에서도 객체들이 안죽고 버티면 꽉 차게 될텐데 이 때 Full GC나 Major GC라는게 일어난다.
5. 근데 이 경우에 일반적으로 STW(Stop-The-World) 이벤트가 발생해 JVM 내부에서 실행 중인 모든 애플리케이션 스레드(메인 스레드, 사용자 정의 스레드)가 일시적으로 중단된다.


.. 근데 사실 여기서 뭉뚱그려 넘어간게 있는데, 바로 '생존'이라는 표현이다. 생존이 뭘 의미할까?

 

 

5. 늙었다는 건 .. 살아남았다는 것.
a. Old 영역에 들어갔다는 것 .. 살아남았다는 것
[그림8]. 강한자가 살아남는 것이 아니라 살아남는자가 강한 것이다

- 늙었다는 건 살아남았다는 거다.
- 어떻게 하면 JVM에서 살아남을 수 있을까?



b. JVM에서 살아남는 법

[그림9]. 살려면 연결을 끊지 마라

- 쓰레드 .. 그러니까 Stack 영역에 참조를 계속 유지해라. 연결이 끊기면 죽는다.
- 연결된 상태를 Reachable, 끊긴 상태를 Unreachable 상태로 보고 부자(rich)가 아닌놈은 쓰레기통에 버려진다. (Garbage Collector)

 

 

 

오늘 하루도 공부할 수 있어 크게 감사합니다

2024-01-07 개발자 최찬혁