자바는 어떤 운영체제든 실행이 가능하다. 이 기반이 JVM이고 OS에 독립적으로 구동 가능하다.
컴파일러에 의해서 자바가 클래스 파일로 바뀌고 클래스 파일들이 구동될수 있게 클래스 로더같은 JRE를 통해 실행된다. JRE는 JVM과 JAVA API를 구성한다.
자바 코드의 수행 과정이다.
JDK를 사용해 바이트 코드(class파일)를 만들고, JRE를 사용해서 바이트코드를 실행하면, JVM이 가동되어 바이트코드의 실질적인 실행(실제 OS에 메모리 할당 및 회수, 시스템 명령 호출 등)을 담당한다.
클래스 로더가 컴파일된 자바 바이트코드를 런타임 데이터 영역에 로드하고, 실행 엔진이 자바 바이트 코드를 실행한다.
자바의 바이트코드는 JRE위에서 동작한다. JRE에서의 가장 주요 요소는 바이트코드를 해석하고 실행하는 JVM이다. JRE는 JAVA API, JVM으로 구성되며, JVM의 역할은 자바 애플리케이션을 클래스 로더를 통해 읽어 자바 API와 함께 실행하는 것이다.
가상화란 물리적 자원을 논리적으로 분리하여 여러 개의 독립적 가상 환경으로 활용할 수 있게 만드는 기술이다.
가상 메모리는 메인 메모리의 물리적 크기 제한을 극복하기 위해, 보다 저렴한 보조 기억 장치의 공간을 대신 사용하는 기법이다. ram과 hdd의 공간을 사용하는 것을 보통 가상 메모리라 한다.
현대 컴퓨터 시스템에서는 필요한 메모리 페이지만 로드 하여 메모리 효율을 높이는 페이징 기술을 통해 가상 메모리 주소의 개념을 동작시킨다.
가상 메모리 주소는 프로그램 실행에 있어 독점적 공간을 제공하며, 다른 프로그램과 주소가 겹치지 않도록 보장 한다.
가상 메모리는 가상 메모리 주소를 물리 메모리 주소로 매핑하며, MMU (Memory Management Unit)와 TLB (Translation Lookaside Buffer)를 사용하여 변환 오버헤드를 최소화한다.
가상 머신(Virtual Machine)은 하드웨어 대신 소프트웨어를 사용해 프로그램을 실행하고 애플리케이션을 배포하는 물리적 컴퓨터의 가상 표현 또는 에뮬레이션이다.
간단하게 독립적인 OS를 가지는 가상 컴퓨팅 환경이다.
가상 머신은 다음과 같은 구성 요소를 포함한다:
가상화는 가상 머신이 독립적으로 실행될 수 있도록 가상 메모리 관리 를 포함한 다양한 자원 가상화 기술을 활용한다.
하이퍼바이저는 Shadow Page Table 이나 Extended Page Table(EPT) 과 같은 기술을 통해 가상 머신이 효율적으로 메모리 주소를 변환하고 사용할 수 있도록 돕는다.
가상화에 대해서는 도커와 컨테이너, 클라우드와 함께 다룰 예정이다.
Java 플랫폼은 Java 소프트웨어 개발 언어로 작성된 프로그램을 위한 실행 환경입니다. "한 번 작성하면 어디서나 실행할 수 있다"는 Java의 약속은 모든 Java 프로그램이 모든 Java 플랫폼에서 실행될 수 있다는 것을 의미하며, 이 때문에 Java 플랫폼에는 Java 가상 머신(JVM)이 포함되어 있습니다.
Java 프로그램에는 JVM을 위한 명령어의 한 형태인 바이트코드가 포함되어 있습니다. JVM은 이 바이트코드를 호스트 컴퓨터에서 사용하는 가장 낮은 수준의 언어인 머신 코드로 컴파일합니다. 한 컴퓨팅 플랫폼의 Java 플랫폼에 있는 JVM은 프로세서가 기대하는 머신 코드에 따라 다른 컴퓨팅 플랫폼의 JVM과 다른 머신 코드 명령어 세트를 생성합니다.
따라서 JVM은 전체 OS를 실행하지 않으며 다른 VM처럼 하이퍼바이저를 사용하지 않습니다. 대신 특정 하드웨어에서 실행되도록 애플리케이션 수준의 소프트웨어 프로그램을 변환합니다.
JVM(Java Virtual Machine)은 소프트웨어 기반의 가상 머신이으로 CPU랑 메모리(RAM, SSD 등)를 가상화해서, 프로그램이 실제 하드웨어에서 실행되는 것처럼 작업한다. 기본적으로 자바 바이트코드(클래스 파일)를 실행하는 실행기이다.
단순히 자바 코드를 실행하는 것만 하는 게 아니라, 운영 체제 기능의 일부도 포함해서 프로그램 실행 환경을 제공한다. ex), 메모리 관리(가비지 컬렉션), 스레드 관리, 로드 타임(dynamic class loading)
1. 바이트코드 실행
.class 파일)을 실행. .java)를 자바 컴파일러(javac)로 변환하면 플랫폼에 독립적인 바이트코드가 생성. 2. 메모리 관리
JVM은 힙 메모리와 스택 메모리를 가상으로 관리.
힙(Heap): 객체랑 클래스가 저장되는 영역.
스택(Stack): 메서드 호출이랑 지역 변수가 저장되는 영역.
가비지 컬렉션(Garbage Collection)을 통해 사용하지 않는 객체를 자동으로 정리.
3. 운영 체제 추상화
JVM은 자바 프로그램이 특정 운영 체제에 의존하지 않게, 운영 체제와의 상호작용을 추상화를 통해 독립적으로 사용.
4. 보안
JVM은 클래스 로더(Class Loader)와 바이트코드 검증 기능으로 프로그램 실행 시 보안을 유지한다. 그래서 악의적인 코드가 시스템에 접근하는 걸 방지.
소스 코드 작성
.java 파일로 자바 소스 코드를 작성.
컴파일
자바 컴파일러(javac)로 바이트코드로 변환해서 .class 파일을 생성.
클래스 로딩
JVM이 클래스를 로드해서 메모리에 배치.
바이트코드 실행
JIT 컴파일러로 바이트코드를 네이티브 코드로 변환하거나, 인터프리터 방식으로 실행.
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
컴파일
bash
코드 복사
javac HelloWorld.java -> HelloWorld.class
실행
bash
코드 복사
java HelloWorld -> JVM이 HelloWorld.class 실행해서 결과 출력.
JVM 덕분에 자바 프로그램은 플랫폼 독립적으로 실행가능.
이외에도 성능 최적화, 보안 같은 다양한 기능을 제공한다.
JDK는 Java 소스 코드를 바이트코드(.class)로 컴파일하고, JRE는 이 바이트코드를 실행한다.
JVM은 클래스 로더와 실행 엔진을 통해 바이트코드를 로딩 → 링크 → 초기화 → 실행한다.
실행 엔진은 인터프리터, JIT 컴파일러, GC 등을 활용해 바이트코드를 실행하고 메모리 관리를 수행합니다.
jvm은 os로부터 메모리를 할당받는데 이 메모리를 자바 프로그램에 맞게 여러 영역으로 나누어 사용하게 된다. 크게 3가지 영역이다.
1. Heap 영역
2. Stack 영역
3. Static(Method) 영역
변수는 선언 위치에 따라 구분짓게 된다.
4가지 종류로 클래스 변수, 인스턴스 변수, 지역 변수, 매개 변수
public class Variable {
public static int age = 30; // 클래스 변수(전역 변수)
int height = 175; // 인스턴스 변수 (전역 변수)
public static void main(String[] args) { //매개 변수(파라미터)
int size = 50; //지역 변수
}
}
인스턴스를 생성할 때 사용되는 메모리 영역
Person person = new Person("HONG");
person은 stack 영역, new 객체 부분은 Heap 영역에 저장된다.
흐름
1. new를 사용해 객체를 생성할 때 저장한다.
2. 참조형 데이터 타입이 저장 (String, array, enum, class, interface), Object
3. Heap 영역의 데이터들을 가르키는 Reference(참조주소)는 Stack 영역에 적재되고 Reference를 통해서만 Heap 영역의 데이터에 접근, 관리 가능하다.
4. 호출이 종료되도 삭제되지 않는다. 가비지컬렉터 에 의해서 메모리에서 해제된다.
5. 쓰레드가 몇 개가 존재하든, 단 하나의 영역만 존재한다. stack 영역의 경우 쓰레드 별로 1개씩 생성
클래스 변수나, static으로 선언된 것들이 static 영역에 저장된다.
가비지 컬렉션은 가비지(유효하지 않는 메모리) 즉, 불필요한 메모리를 자동으로 관리해 준다.
Person person = new Person();
person.add("hello")
person = null;
person = new Person();
person.add("world");
위와 같은 경우 hello를 가진 person 객체는 더 이상 참조를 하지 않고 사용되지 않아 가비지가 된다. 이런 메모리 누수를 방지하기 위해 가비지 컬렉터가 주기적으로 검사하여 메모리를 관리한다.
JVM의 Heap 영역은 처음 설계 당시 2가지 전제로 설계되었다.
Garbage Collection은 객체가 garbage인지 판별하기 위해 객체애 유효가 참조가 있다면 Reachable, 없으면 unreachable로 구별한다. Unreachable 객체를 가비지로 간주하여 gc를 수행한다.
위 사진과 같이 뿌리부터 root set으로 시작한 참조 사슬에 속한 객체들은 reachable 객체이고 나머지는 Unreachable 객체로 가비지 컬렉터의 대상이다.
가비지의 동작 과정에서 Stop the world
객체가 새롭게 생성되면 young 영역 중에서도 Eden 영역에 할당된다. 이 영역이 차게 되면 Minor GC가 발생하게 되는데
Young 영역에서 오래 살아남은 객체는 Old 영역으로 Promotion 되었다. 그리고 Major GC는 객체들이 계속 Promotion 되어 Old 영역의 메모리 부족시 발생한다. young 영역은 할당 크기가 작기 때문에 짧은 시간에 GC가 끝난다. 그러나 Old 영역은 할당된 영역 크기가 크고, Young 영역을 참조 할 수 있기에 시간이 오래 걸린다.
Serial GC
Parallel GC
Parallel Old GC(Parallel Compacting GC)
Concurrent Mark & Sweep GC(이하 CMS)
G1(Garbage First) GC
등이 존재한다.
각 서비스의 WAS에서 생성하는 객체의 크기와 생존 주기가 모두 다르고, 장비의 종류도 다양하다. WAS의 스레드 개수와 장비당 WAS 인스턴스 개수, GC 옵션 등은 지속적인 튜닝과 모니터링을 통해서 해당 서비스에 가장 적합한 값을 찾아야 한다. - JavaOne에서 Oracle JVM을 만드는 엔지니어
참조: Naver D2