JVM 전반에 관하여, 메모리 관리와 GC
2024. 1. 28. 18:33ㆍBackend 취업준비/Java
자바 SE(Standard Edition) 의 구현체는 JDK(자바 개발 키트)와 JRE(자바 실행 환경)로 구성된다
- JDK => 프로그램 개발에 필요한 JVM + 라이브러리API + 개발도구(java.exe, javac.exe) , JDK를 설치하면 JRE도 자동 설치된다.
- *.java => (javac.exe) => *.class => (java.exe) => 기계어 => 실행
- JRE => 프로그램 실행에 필요한 JVM + 라이브러리API
- 자바 프로그램을 개발하고자 하는 것이 아닌 이미 개발된 프로그램을 실행만 하려면 설치
JVM
- 운영체제, 다른 애플리케이션은 컴퓨터의 메모리 위에서 돌아간다. Java프로그램을 실행하면 실행을 위한 메모리를 할당받아 Runtime Data Area 구성.
- 자바 소스코드를 작성하고, 자바 컴파일러를 통해 컴파일을 하면 .class라는 확장자를 가진 파일이 생성된다. 또한, java 명령으로 해당 클래스 파일을 실행하면, JVM은 클래스 로더를 통해 클래스 파일을 읽어들인다.
- javac.exe => 자바 컴파일러.*.java 소스파일을 *.class 클래스 파일로 변환
- 클래스 파일 안에는 클래스 안에 어떤 필드가 몇개 선언되어 있는지, 메서드는 몇개고 이름은 뭔지, 바이트코드 등 클래스에 대한 모든 정보가 들어있다.
- java.exe => 바이트 코드(JVM이 알아들을 수 있는 명령어 집합)를 기계어로 번역해서 CPU에 일을 시킴. 인터프리터가 해당 역할 수행, 자바 프로그램(클래스 파일)을 실행
- javac.exe => 자바 컴파일러.*.java 소스파일을 *.class 클래스 파일로 변환
- 이때, 미리 설치된 JVM 은 작성된 프로그램이 운영체제가 다르더라도 동일하게 작동하도록 환경을 제공한다. => 운영체제에 맞춰 컴파일해야하는 다른 언어에 비해 높은 이식성의 장점을 가진다.
Class Loader
- Java Compiler 를 통해서 .class 확장자를 가진 클래스 파일은 각 디렉터리에 흩어져 있다. 또한, 기본적인 라이브러리의 클래스 파일들은 $JAVA_HOME 내부 경로에 존재한다. 각각의 클래스 파일들을 찾아서 JVM 의 메모리(Runtime Data Area)에 탑재해주는 역할을 하는 것이 바로 ClassLoader 의 역할.
- ClassLoader 는 클래스 파일을 찾아서 탑재하는 역할뿐만이 아니라 jvm 에 관련된 다른 일들도 같이한다.
- ClassLoader 는 크게 Loading, Linking, 그리고 Initialization 3가지 역할을 맡는다.
- Loading
- ClassLoader 가 필요한 클래스 파일들을 찾아 탑재한다. 각각의 클래스 파일들이 기본으로 제공받는 클래스 파일인지 혹은 개발자가 정의한 클래스 파일인지와 같은 기준에 의해서 세가지의 수준으로 나뉜다.
- Bootstrap ClassLoader => 다른 모든 ClassLoader 의 부모가 되는 ClassLoader.
- rt.jar 를 포함하여, JVM 을 구동시키기 위한 가장 필수적인 라이브러리의 클래스들을 JVM 에 탑재한다. 가장 상위의 ClassLoader 이므로 다른 ClassLoader 와는 다르게 탑재되는 운영체제에 맞게 네이티브 코드로 쓰여있습니다
- Extensions ClassLodaer, Application ClassLoader
- ClassLoader 가 필요한 클래스 파일들을 찾아 탑재한다. 각각의 클래스 파일들이 기본으로 제공받는 클래스 파일인지 혹은 개발자가 정의한 클래스 파일인지와 같은 기준에 의해서 세가지의 수준으로 나뉜다.
- Liniking : 로드된 클래스 파일들을 검증하고, 사용할 수 있게 준비하는 과정. Verification , Preparation , 그리고 Resolution 이라는 세 가지 단계로 이루어진다.
- Initialization : 클래스 파일의 코드를 읽는다. Java 코드에서의 class 와 interface 의 값들을 지정한 값들로 초기화 및 초기화 메서드를 실행시킨다. 이때, JVM 은 멀티 쓰레딩으로 작동을 하며, 같은 시간에 한 번에 초기화를 하는 경우가 있기 때문에 초기화 단계에서도 동시성을 고려해주어야 한다. Class Loader 를 통한 클래스 탑재 과정이 끝나면 본격적으로 JVM 에서 클래스 파일을 구동시킬 준비가 끝난다.
Runtime Data Area
- Method Area
- JVM 의 모든 Thread가 공유하는 영역, JVM 구동 시작 시에 생성이 되며, 종료 시까지 유지되는 공통 영역
- 클래스에 대한 모든 정보가 저장 된다. 인스턴스 생성을 위한 객체 구조, 생성자, 필드 등이 저장. Runtime Constant Pool 과 static 변수, 그리고 메소드 데이터와 같은 Class 데이터들도 이곳에서 관리
- JVM 의 다른 메모리 영역에서 해당 정보에 대한 요청이 오면, 실제 물리 메모리 주소로 변환해서 전달
- Heap
- JVM 에서 하나만 생성이 되고, 해당 영역 데이터는 모든 Java Stack 영역에서 참조, Thread 간 공유.
- 런타임에 생성되는 모든 객체들이 저장(문자열에 대한 정보를 가진 String Pool 뿐만이 아니라 실제 데이터를 가진 인스턴스, 배열 등)
- Heap 영역이 가득 차게 되면 OutOfMemoryError 를 발생시키게 됩니다. => Garbage Collector의 주요 작동 영역
- JVM Stack
- 각 Thread 별로 따로 할당되는 영역. Thread 별로 메모리를 따로 할당하기 때문에 동시성 문제에서 자유롭다.
- 메서드를 실행하기 위한 정보들이 저장되는 공간. 각 Thread 들은 메서드를 호출할 때마다 Frame 이라는 단위를 추가(push)한다. 메소드가 마무리되며 결과를 반환하면 해당 Frame 은 Stack 으로부터 제거(pop)가 된다.
- Frame 은 메서드에 대한 정보를 가지고 있는 Local Variables Array, Operand Stack 그리고 Constant Pool Reference 로 구성이 되어 있다.
- Local Variables Array은 메소드의 매개변수, 지역 변수들을 담고 있는 배열. 인스턴스 메서드일 경우 첫번째 인덱스에 현재 인스턴스에 대한 참조를 가지고 있다.
- Operand Stack 은 메소드 내 연산을 위해서, 바이트 코드 명령문들이 들어있는 공간. JVM은 Stack을 기반으로 연산을 수행하는데 피연산값 혹은 연산의 중간 값들을 저장하기 위한 자료구조.
- Constant Pool Reference 는 Constant Pool 참조를 위한 공간.
- PC Register
- 현재 실행되고 있는 명령어의 주소를 저장하고 있는 곳
- 멀티 쓰레드 프로그래밍 환경에서 한 쓰레드에서 작업하다가 다른 쓰레드로 잠시 CPU 점유를 넘겨주고,다시 돌아왔을 때, 이전에 어떤 명령을 수행하고 있었는지 기억하고 있어야, 이전 작업을 다시 이어서 수행할 수 있을 것이다.
- Native Method Stack
- C나 C++로 작성된 메서드를 실행할 때 사용되는 Stack
- JVM Stack, PC Register, Native Method Stack 세 개의 영역은 쓰레드가 생성될 때 마다 같이 생성이 되고, 서로 다른 쓰레드가 침범할 수 없는 영역이다.
메모리 관리와 GC(Garbage Collector)
- JVM 상에서 동적으로 할당된 메모리 영역 중 더 이상 사용되지 않는 영역을 탐지하여 해제하는 기능
- Stack : 정적으로 할당한 메모리 영역, 원시 타입의 데이터가 값과 함꼐 할당, Heap영역에 생성된 Object타입의 데이터의 참조 값 할당. (frame이 pop되면서 삭제된다)
- Heap : 동적으로 할당한 메모리 영역, 모든 Object 타입의 데이터가 할당, Heap영역의 Object를 가리키는 참조변수가 Stack에 할당. ( frame이 pop되면서 삭제되고, 더이상 참조되지 않는 객체일 경우 GC가 삭제)
- 참조되고 있는지에 대한 개념을 reachability 라고 하고, 유효한 참조를 reachable , 참조되지 않으면 unreachable 이라고 한다. 그리고 GC는 unreachable 한 객체들을 garbage 라고 인식하게 된다.
- Garbage Collection 과정 정리 (1, 2 를 Mark과정, 3을 Sweep과정이라 칭하여 Mark and Sweep 알고리즘 이라 한다)
- Gabage Collector가 Stack의 모든 변수를 스캔하면서 각각 어떤 객체를 참조하고 있는지 찾아서 마킹한다.
- Reachable Object가 참조하고 있는 객체도 찾아서 마킹한다.
- 마킹되지 않은 객체를 Heap에서 제거한다.
- JVM 에서 자동으로 동작하기 때문에 Java 는 특별한 경우가 아니면 메모리 관리를 개발자가 직접 해줄 필요가 없다.
stop-the-world
- JVM 은 GC 를 통해 JVM 에서의 여유 메모리를 확보 한다. 이 때문에 GC 를 자주 실행시키면, 여유 메모리를 최대한 확보하여 성능이 좋아지리라 추측할 수도 있다. 하지만 빈번한 GC 는 프로그램의 성능을 저하 한다.
- GC 가 일어나면 GC 를 담당하는 쓰레드를 제외한 모든 쓰레드들은 작동이 일시적으로 정지되며, 이를 Stop-The-World 현상이라고 한다.
- 모든 쓰레드가 정지되기 때문에 더 이상 작업이 실행되지 않고, 성능이 저하된다. 그래서 적절한 빈도의 GC 가 실행되도록 하여, Stop-The-World 시간을 줄여 쓰레드가 정지되는 시간을 줄이는 것이 중요하다.
위의 특징들로 생기는 장단점
- 장점
- 메모리 누수가 발생되지 않음
- 휴먼 에러 발생 가능성 낮춤 (해제된 메모리에 업근시도 or 메모리 이중 해제)
- 단점
- 성능저하 (어떤 메모리를 해제해야 할지 검사하고 삭제하는 이 과정 또한 결국 CPU자원과 메모리를 필요로 한다)
- 개발자가 언제 메모리가 해제되는지 모른다. (JVM은 GF를 실행시키기 위해 잠시 어플리케이션 실행을 멈춘다. 실시간성이 매우 강조된다면, 이런 특징이 적합하지 않을 수 있다.)
Heap영역의 구조
- Heap은 young generation, old generation으로 나뉜다.
- young generation : 새로운 객체들이 할당
- old generation : young generation에서 오랫동안 살아남은 객체들이 존재
- generation이 둘로 나뉘는 이유
- 할당된 객체는 오랫동안 참조되지 않는 것이 대부분이다. 즉, 금방 garbage상태가 된다.
- 반대로 오래된 객체에서 젊은 객체로의 참조는 거의 없다.
- 따라서 Heap이 하나라면, 오래된 객체까지 스캔하는 것(major gc)이 비효율 적이다.
- 해당 이유로 두개의 구역으로 나누고, 할당되지 않은 객체들을 주기적으로 스캔하는 것(minor gc)이 훨씬 효율적이다.
출처
(도서) 이것이 자바다
https://www.linkedin.com/pulse/jvm-architecture-how-internally-work-ali-as-ad
https://tecoble.techcourse.co.kr/post/2021-07-12-jvm-jre-jdk/
https://tecoble.techcourse.co.kr/post/2021-07-15-jvm-classloader/
https://tecoble.techcourse.co.kr/post/2021-08-09-jvm-memory/
https://tecoble.techcourse.co.kr/post/2021-08-30-jvm-gc/
https://www.youtube.com/watch?v=GU254H0N93Y
'Backend 취업준비 > Java' 카테고리의 다른 글
불변 클래스, 불변 객체, String, 래퍼 클래스 (2) | 2024.02.01 |
---|---|
Java 여러 쟁점 정리 (0) | 2024.01.30 |
NaN과 Infinity 연산, null 출력 (0) | 2024.01.30 |
자바의 String 클래스, 문자열 다루기 (0) | 2024.01.29 |
왜 public static void main 인가? (0) | 2024.01.28 |