Java의 플랫폼
- Java SE (Java Standard Edition)
- Java SE의 API는 Java 프로그래밍 언어의 핵심 기능을 제공
- 기본 API 및 네트워킹, 보안, 데이터베이스 액세스 등에 사용되는 고급 클래스에 이르기까지 모든 것을 정의
- Java EE (Java Enterprise Edition)
- 주로 웹 및 엔터프라이즈 애플리케이션을 개발하는데 사용되는 엔터프라이즈 플랫폼이며, Java SE 플랫폼 위에 구축
- Servlet, JSP, Web Service, EJB, JPA 등의 기술들을 사용
- Java ME (Java Micro Edition): 모바일 애플리케이션 전용 마이크로 플랫폼
- Java FX
- Java FX Script로 작성된 풍부한 인터넷 응용 프로그램을 만들기 위한 플랫폼
- Java FX 플랫폼으로 작성된 응용 프로그램은 Java EE 플랫폼 서비스의 클라이언트일 수 있다.
Java 플랫폼의 Product
- JRE (Java Runtime Environment): 자바 실행 환경으로 자바 애플리케이션 실행되기 위한 최소환경(.class 파일이 동작할 수 있는 환경)으로 Java 애플리케이션을 실행하기 위한 라이브러리, 클래스 로더, JVM 및 기타 구성요소를 제공한다.
- JDK (Java Development Kit): JRE의 상위 집합으로 자바 개발 도구로 JVM, API, Compiler, Tools, API document가 포함된다.
- JVM (Java Virtual Machine): 자바 애플리케이션을 해석하고, 로드하여 실행하는 가상머신으로 하드웨어 및 OS 독립성을 제공한다.
- JRE = JVM + Library classes
- JDK = JRE + Developer tools
JVM Architecture
JVM 내부 구조는 Class Loader, Memory Area, Execution Engine 등을 포함하고 있다.
동작 흐름
- Class Loader는 Java 애플리케이션 실행시마다 클래스 파일을 로드
- Execution Engine을 통해 Bytecode를 해석(Interpret)
- Runtime Data Area에 메모리 등의 리소스 할당 후 동작
- 필요에 따라 Garbage Collector를 통한 메모리 관리 및 Thread 등이 동작
Class Loader
- Class를 Load하고 Link를 통해 적절히 배치하는 일련의 작업을 수행
- Class는 참조되는 순간 동적으로 Load 및 Link가 이루어진다 (Dynamic Loading)
- Load가 어느 시점에 수행되느냐에 따라 LoadTime Dynamic Loading과 Runtime Dynamic Loading으로 구분된다.
- JVM에 Namespace를 이용하여 이미 로딩된 클래스는 로딩하지 않는다.
- JVM 기동시 기본적으로 로딩
- Bootstrap ClassLoader를 생성(부모를 가지지 않는 최상위 ClassLoader로 Native code로 구현되었으며, 런타임환경 구성의 기초단계)
- Java Runtime Library (rt.jar)를 로딩
- java.lang.Object class를 로딩
- Class loading 과정은 java 애플리케이션 실행시 -verbose(상세 정보 출력용) 옵션을 주면 확인할 수 있다.
- 실행 방법: 인텔리제이의 하단의 터미널 탭 아래 명령을 실행한다. 로그를 보면 필요한 런타임 클래스(rt) 먼저 로드되는 것을 확인할 수 있다.
- cd out/production/EduExample (인텔리제이 툴에서는 java 소스를 저장하면 out/production/프로젝트명 폴더에 클래스가 컴파일되어 생성)
- java -verbose ch01.Board
Execution engine
실행 엔진에는 다음과 같은 것들이 포함되어 있다.
- Interpreter(인터프리터): Bytecode(.class 파일)를 기계(OS에 맞는)가 이해할 수 있는 형태(native code)로 해석하고 명령을 실행한다.
- JIT(Just-In-Time) Compiler: Java의 인터프리터는 런타임에 Bytecode를 매번 읽어들여 해석하는 느린 속도를 해결하기 위해 사용된다.
- 자주 실행되는 코드를 파악 → 동일한 코드를 매번 해석하지 않고 기계어로 컴파일해서 캐싱하여 재사용 → 성능을 향상시킨다.
- 실행 기록의 통계 정보로 자주 사용되는 코드들을 확인한다.
- Garbage Collector: 더 이상 사용되지 않는 Object(객체)들을 수집하고 제거하는 역할을 수행한다.
Interpreter
- 로딩된 클래스가 Execution Engine을 거치게 되면 내부적으로 Bytecode를 Instruction(명령어)으로 변경하여 이를 수행한다.
- Interpreter
- Bytecode를 하나씩 읽고 해석하여 실행하는 방식으로 실행시간이 많이 거린다.
- 기본 하드웨어를 적절하게 호출한다.
- 초창기의 JVM은 실행 속도에 있어 약점이 있었다.
- Bytecode verifier(바이트코드 검증기): 게이트키퍼의 역할로 코드 포맷을 확인하고 불법 코드를 확인하는 바이트코드 검증기의 테스트를 통과한 경우에만 코드가 인터프리트 될 수 있다.
JIT(Just-In-Time) Compiler
- JIT Compiler 방식은 Bytecode로부터 Native code(OS가 해석 가능한 기계어)를 생성한 뒤 실행함
- 아래 그림과 같이 java 소스가 ByteCode로 변경된 후 JIT Compiler 통하면 Native Code로 변경되어 실행됨
- 실행시간은 빠르나 Native code로 compile 하는 시간이 길다.
- 기본적으로 Cache가 되기 때문에 반복 호출 시(Loop문) 성능이 극대화 됨. 그렇지 않다면 Interpreter보다 성능이 떨어질 수 있다.
- 그렇기 때문에 Interpreter를 사용하다가 일정한 기준을 넘어서게 되면 JIT Compiler를 가동하는 방식(Lazy Fashion)을 사용
Garbage Collector
- 시스템 레벨의 데몬 스레드에 의해 Garbage Collector Thread가 자동적으로 더 이상 사용하지 않는 데이터를 메모리로 반환하는 메모리 관리를 해주기 때문에 별도의 코드가 필요 없다.
- 자동으로 메모리 관리를 해주기 때문에 개발자는 비즈니스 코드에 집중할 수 있다.
- GC 지원으로 메모리로 인한 System이 crash 되는 일이 타 언어에 비해 많이 줄었다. (C언어에서는 메모리 관리를 개발자가 직접 해줘야 한다)
- GC를 지원한다고 해서 메모리 부족으로 시스템이 다운되는 현상이 없어지는 것은 아니다.
- 대량의 라이브 객체가 있을 경우 GC로도 메모리 확보가 안 될 수 있다.
- 전체 메모리가 아닌 특정한 객체 단위의 Operation이다.
- 개발자가 코드를 삽입하여 메모리 해제 할 수 없다. 대신 필요시 GC Option이라는 것을 이용하여 GC의 메모리 수집 방식을 조정하여 애플리케이션을 운영할 수 있다. Java 11버전에서는 G1 GC 알고리즘을 기본으로 사용한다.
- GC 알고리즘: Serial GC, Parallel GC, CMS(Concurrent Mark Sweep) Collector, G1 GC
- Garbage 대상은 Heap과 Method Area에서 현재 사용되지 않고 있는 객체(참조되지 않는 객체)를 의미한다.
- Java에서는 객체 인스턴스가 생성된 후 시간에 따라 5가지 세대로 나눠진다.
- Eden → Survivor0 → Survivor1 → Old (왼쪽 방향일수록 신생 데이터)
- Metaspace는 OS레벨에서 관리하는 Native memory 영역으로 클래스, 메소드, 상수 풀 정보 등의 메타 정보들이 저장되며 사이즈에 제한이 없다(Java 8 버전 이전에는 Permanent 영역으로 사이즈 제한으로 인해 메모리 부족 현상 오류가 발생했었다).
Memory Area
Class(Method) Area
Runtime의 Constant pool(상수 풀), Field, Method data, Method에 대한 코드, 클래스별 구조를 저장한다.
Heap Area
- Runtime의 객체가 할당되는 데이터 영역이다.
- Heap 영역의 사용되지 않은 객체들을 GC에 의해 자동으로 메모리에서 제거된다.
- Heap 영역은 Java의 객체(인스턴스)와 Array(배열)가 저장되는 공간
- 모든 Thread들에 의해 공유되며, 동기화 이슈가 발생할 수 있다.
- Heap의 메모리 해제는 Garbage Collection을 통해서 수행된다.
Stack Area
- 주로 로컬 변수(Local variable)를 저장하는 데이터 영역으로 Heap 객체에 대한 참조를 포함하기도 한다.
- 각 스레드별로 Private JVM Stack이 생성된다.
Runtime Data Area
- PC(Program Counter) Register: Java는 Stack-Base로 작동되며, 현재 작업하는 내용을 CPU에 Instruction(명령)으로 제공해야 하는데 이를 위한 버퍼공간으로 PC Register라는 메모리 영역을 생성한다.
- Java Stack: 로컬 변수(Local Variable)를 저장하는 공간
- 메소드가 호출될 때마다 Stack Frame이라는 새로운 데이터 영역이 생성된다.
- 메소드 실행이 완료되면 Stack Frame이 사라진다.
- Stack Frame이 모여 Java Stack이 되며, 최상단은 Active Java Stack이 된다.
- 하나의 Thread에 각각의 Java Stack이 존재한다.
Native method interface
Native Method Libraries와 상호작용하며 Execution Engine을 위해 Native Libraries를 제공한다.
Native Method Stack
응용 프로그램(Application program)에서 사용되는 Native method 정보를 가지고 있고, 각 스레드별로 개별 Native method stack이 생성된다.
- JVM Method와 같은 방식으로 사용된다.
- JVM 명령이 아닌 다른 언어를 사용해서 구현(보통 C언어)
- JNI(Java Native Interface)를 이용해서 다른 언어와 연동할 수 있다.
- JNI는 C, C++, 어셈블리와 같은 다른 언어로 개발된 애플리케이션과 통신하기 위한 인터페이스를 제공하는 프레임워크로 이를 이용하여 OS 라이브러리 또는 소프트웨어 라이브러리와 상호 작용하는데 이용된다.