📍 Java를 쓴다면 JVM 에 관하여도 알아야한다.
왜 우리는 JVM 에 관하여 알아야할까?
먼저 Java의 프로그램은 어떻게 실행(및 컴파일) 되는지 생각해보자.
(*컴파일 : 사람이 작성한 언어를 기계가 읽을 수 있도록 번역 )
(*빌드 : 문서나 리소스 등을 하나로 합쳐 앱으로 실행되게끔 하는 과정이나 결과물 (컴파일 포함) )
(*실행 : RUN. 빌드가 완성된 앱을 실제 시뮬레이터나 디바이스에 실행시키는 과정 (빌드 포함) )
- 프로그램이 실행되면 JVM은 OS로부터 프로그램에 필요한 메모리를 할당받는다.
- 자바 컴파일러(
javac
)가 자바 소스코드(.java
)를 읽어들여 자바 바이트코드(.class
)로 변환시킨다.- 클래스 로더(Class Loader)를 통해 class 파일들을 JVM으로 로딩한다.
- 로딩된 class 파일들은 실행 엔진(Execution engine)을 통해 각 OS에 맞는 기계어로 해석된다. (컴파일)
- 해석된 바이트 코드는 실행 메모리 공간(Runtime Data Area)에 배치되어 수행된다.
동일한 기능의 프로그램이더라도 메모리 관리에 따라 성능은 좌우 된다.
위 과정에서 Java 프로그램이 실행되었을 때 JVM은 OS로부터 프로그램이 필요로 하는 메모리를 할당받는다.
또한 필요에 따라 Thread Synchronization(스레드 동기화) 과 GC 같은 관리 작업을 수행하며 메모리를 관리한다.
✔︎ 그러므로 메모리 구조를 알아야 한정된 메모리를 효율적으로 사용할 수 있으므로 자바 가상머신(JVM)을 알아야한다.
JVM(Java Virtual Machine)이란?
자바 가상 머신으로, OS에 종속받지 않고 CPU 가 Java를 인식하고 실행할 수 있게 하는 가상 컴퓨터이다.
(*가상머신 : 프로그램을 실행하기 위해 물리적인 머신과 유사한 머신을 소프트웨어로 구현한 것)
레지스터 기반이 아닌 스택 기반으로 동작한다.
Java 소스코드(
.java
)는 CPU가 인식을 하지 못하므로 기계어로 컴파일을 해줘야한다.
이때, Java는 JVM을 거쳐서 OS에 도달하기 때문에 우선 JVM이 인식할 수 있는 Java bytecode로 변환해줘야한다. Java compiler 가.java
파일을 Java bytecode로 변환한다.
이때 변환된 자바 바이트 코드가.class
파일이다.
(JVM은 이제 실행 엔진을 통해 OS가 bytecode를 이해할 수 있도록 각 OS에 맞는 기계어로 해석해준다.)
[요약] 자바에서 소스를 작성하면 .java파일이 생성되고 .java소스를 컴파일러가 컴파일하면 .class파일이 생성된다.
JVM 은 크게 클래스 로더
, 실행 엔진
, 런타임 데이터
영역으로 이루어져있다.
자바는 동적 로딩을 지원한다.
컴파일 타임(프로그램 실행 초기)이 아니라 런 타임(프로그램 실행 중간, 바이트 코드를 실행할 때)에 동적으로 클래스 파일을 읽어온다는 것이다.
즉, 자바 클래스들은 시작 시 한번에 로드되지 않고, 애플리케이션에서 필요할 때 로드된다.
(*동적 로딩 : 프로그램을 실행하는 도중에 새로운 클래스를 로딩하여 사용)
이때, 클래스 로더가 런 타임에 클래스를 동적으로 JVM에 로드 하는 역할을 수행한다.
클래스 로더는 로딩, 링크, 초기화 단계로 나뉘어져 있다.
JVM 내로 class 파일을 로딩하며 읽고 저장하고, 링크를 통해 배치하는 작업을 수행한다.
클래스를 실행 시키는 역할이다.
클래스 로더를 통해 JVM 내의 Runtime Data Area에 배치된 바이트 코드들을 명렁어 단위로 읽어서 실행한다.
JVM의 메모리 영역으로, 프로그램을 수행하기 위해 OS에서 할당받은 메모리 공간이다.
크게 보자면 Heap, Method area, Thread 마다 생성되는 영역으로 볼 수 있는데,
이때 Heap, Method area는 모든 스레드가 공유하는 메모리 공간으로 GC의 대상이다.
Program Counter register
Thread가 시작될 때 생성되며 생성될 때마다 생성되는 공간으로, 스레드마다 하나씩 존재한다.
각 스레드가 메서드를 실행할 때, pc는 그 메서드 안에서 몇 번째 줄을 실행해야할지 등을 나타내는 역할을 한다.
JVM stack
메소드가 호출될 때마다 stack 안에 각각의 stack frame
(그 메서드만을 위한 공간)이 새로 생성된다 (push).
(메소드 호출의 트레이스라고 볼 수 있다)
호출된 메서드의 정보 (매개변수, 지역변수, 리턴 값, 연산 시 일어나는 값 등)들을 임시로 저장한다.
메서드 수행이 끝나면 프레임 별로 삭제한다 (pop).
Native method stack
Java Bytecode가 아닌 다른 언어(성능 향상을 위해)로 작성된 코드를 위한 공간이다.
Heap
프로그램을 실행하면서 생성한 모든 객체를 저장한다. (Method area에 올라온 클래스들만 객체로 생성할 수 있다.)
인스턴스를 생성할 때 생성되는 메모리 형식으로, new
연산자 로 생성되는 객체와 배열을 저장한다.
GC의 역역으로 GC가 참조되지 않는 메모리를 확인하고 제거한다.
Heap 구조
Heap Area는 크게 Young, Old 2가지 영역 으로 볼 수 있다.
◦
Young Generation
| 새롭게 생성된 객체가 할당(Allocation)되는 영역
◦Old Generation
| Young 영역에서 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역→ GC의 작업은 Young 영역에 대한
Minor GC
와 Old 영역에 대한Major GC
로 구분된다.
→ 대부분의 객체가 금방 Unreachable 상태가 되기 때문에(메서드 끝나면 stack pop), 많은 객체가 Young 영역에 생성되었다가 사라진다. Old 영역에 할당된 메모리가 허용치를 넘게 되면 Old 영역에 있는 모든 객체들을 검사하여 참조되지 않는 객체들을 한꺼번에 삭제하는 GC가 실행되고 시간이 다소 소요된다.
Method area ( = Class Area, Static area )
클래스 로더가 클래스 파일 정보를 읽어오면, 클래스 정보를 파싱해서 저장한다.
클래스 정보를 처음 메모리 공간에 올릴 때 초기화되는 대상을 저장하기 위한 메모리 공간
멤버 변수
, 메소드
, 타입
에 대한 정보 가 저장된다.
상수 자료형
은 별도의 관리영역인 Runtime Constant Pool 에서 저장되어 참조되고 중복을 막는다.
GC(Garbage collection)이란?
가비지 컬렉션은 JVM의 메모리 관리 기법 중 하나이다.
JVM은 GC를 이용하여 동적으로 할당된 메모리(Heap) 영역 중 더는 사용하지 않는 메모리를 자동으로 회수한다.
다만, 개발자가 메모리가 언제 해제되는지 정확하게 알 수 없고
가비지 컬렉션(GC)이 동작하는 동안에는 다른 동작을 멈추기 때문에 오버헤드가 발생할 수 있다는 단점이 있다.
(*동적 메모리 할당 : Heap. Runtime
시(프로그래머에 의해) 할당(new),소멸(delete) / 모든 Object 타입의 데이터가 할당. )
(*정적 메모리 할당 : Stack. Compile
시 할당, 함수 빠져나가면 소멸 / 원시 타입의 데이터가 값과 함께 할당. Heap 영역에 할당된 Object 타입의 데이터의 참조 값 할당.)
Stack & Heap
main 메서드가 실행되면 순차적으로Stack
에 grade, age 변수가 쌓이게 된다.
그리고 String은 객체임으로Heap
영역에 생기게 되고 "김" 이라는 객체를 참조하는 변수가Stack
에 저장된다.
만약 main 메서드가 끝나게 되면Stack
영역이 pop 되면서 데이터는 사라진다.
그러면Heap
영역에 객체 타입의 데이터만 남게 되며, 가비지 컬렉터의 대상(Unreachable Object)이 된다.
가비지 발생
Student student = new Student(); student.setName("김"); student = null;
"김"으로 생성된 student 객체는 더 이상 참조하지 않고 사용되지 않아서 Garbage(가비지)가 된다.
JVM이 GC을 실행하기 위해 애플리케이션 실행을 멈추고(Stop The World),
GC를 실행하는 쓰레드를 제외한 모든 쓰레드의 작업을 멈춘다.
사용하지 않는 메모리를 제거(Mark and Sweep)하고나면 중단된 작업이 재개된다.
제거하는 과정(Mark and Sweep)을 살펴보자면 다음과 같다.
GC를 수행하기 위해 Stop The World에 의해 애플리케이션이 중지되며 오버헤드가 발생할 수 있다.
Heap의 사이즈가 커지면서 애플리케이션의 지연(Suspend) 현상이 두드러지게 되었고, 이를 막기 위해 어떤 가비지 컬렉션 알고리즘을 선택할 것인지 고민해볼 수 있다.
- Serial GC
- Parallel GC
- CMS(Concurrent Mark Sweep) GC
[참조]
https://asfirstalways.tistory.com/158
https://doozi0316.tistory.com/entry/1%EC%A3%BC%EC%B0%A8-JVM%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EB%A9%B0-%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%8B%A4%ED%96%89%ED%95%98%EB%8A%94-%EA%B2%83%EC%9D%B8%EA%B0%80 /JVM
https://steady-coding.tistory.com/593 /클래스 로더
https://www.youtube.com/watch?v=vZRmCbl871I&t=134
https://d2.naver.com/helloworld/1329
https://mangkyu.tistory.com/118 /가비지