JDK ( Java Development Kit )
자바로 개발하는데 필요한 도구들의 환경설정같은 것이다. JDK 버전은 여러 가지가 있는데, 나중에 쓸일이 있으면 추가적으로 해당 버전을 선택해서 적용해주면 된다. 자바를 개발함에 있어서 필요한 모든 것들의 집합체라고 보면 된다.
JRE를 포함하고 있으며 javadoc, 컴파일러(javac)도 있다. JRE는 java를 실행함에 있어 필요한 환경을 말한다. JVM, java class library, java 명령어 등을 포함하고 있다. 즉 JDK > JRE > JVM같은 느낌이다.
JVM은 클래스파일을 실행할 수 있다. 자바 클래스 로더와 자바 실행 엔진에 의존한다. 자바로 작성된 프로그램의 메모리 관리 및 최적화를 수행한다. 한번 컴파일된 파일은 어떤 운영체제라도 같은 JVM 환경에서 동일하게 동작한다.(WORA)
Amazon Correto는 Java SE 표준과 호환되는 것으로 인증되었다. Corretto를 사용하면 Linux, Windows 및 macOS를 비롯한 인기 있는 운영 체제에서 Java 애플리케이션을 개발하고 실행이 가능해진다.
intelliJ에서 설정시 패스부터 여러모로 알아서 설정해주지만 JDK를 따로 설치했을 경우, 환경변수와 PATH를 설정해줘야한다.(검색해보도록 하자)
JDK는 개발, 실행을 맡고, 그 안에 JRE(Java Runtime Environment)라고 있는데 이건 실행을 맡고있다.
SDK(Software Development Kit) - API와 라이브러리가 함께 제공된 형태의 프로그램을 개발하는데 필요한 여러 보조 프로그램을 포함한다. 자바에서는 JDK를 쓴다.
** 컴파일과정 **
.java -- 컴파일러 --> 바이트코드(.class) --JVM--> 실행
자바 개발을 위한 도구(kit), 자바 컴파일러(javac), 자바가상머신(JVM) 등 자바 개발에 필요한 각 종 Java Library 등을 포함하고 있다. JDK 는 JRE 를 포함하고 있다.(JDK 설치 시 JRE도 설치됨) JDK 는 JRE 에 개발관련 라이브러리가 추가되어 있다.
전체적인 구조는 이런 느낌이다.
- Java 는 플랫폼에 독립적이다.
=> JVM 에 의해 실행되어 Java 자체는 독립성이 보장된다.
사용자 디렉터리 구성요소
- bin : 자바 개발, 실행에 필요한 도구와 유틸리티 명령
- conf : 여러 종류의 패치 파일
- include : 네이티브 코드 프로그래밍에 필요하는 C언어 헤더 파일
- jmods : 컴파일된 모듈 파일들
- legal : 각 모듈에 대한 저작권과 라이선스 파일
- lib : 실행 시간에 필요한 라이브러리 클래스들
bin 디렉터리에 들어 있는 주요한 개발 소프트웨어
- javac : 자바 컴파일러로 자바 소스를 바이트 코드로 변환
- java : 자바 프로그램 실행기 → 자바 가상 기계를 작동시켜 자바 프로그램 실행
- javadoc : 자바 소스로부터 HTML 형식의 API 도큐먼트 생성
- jar : 자바 클래스 파일을 압축한 자바 아카이브 파일(.jar) 생성, 관리
- jmod : 자바의 모듈 파일(.jmd)을 만들거나 모듈 파일의 내용 출력
- jlink : 응용프로그램에 맞춘 맞춤형 JRE 생성
- jdb : 자바 응용프로그램의 실행 중 오류를 찾는 데 사용하는 디버거
- javap : 클래스 파일의 바이트 코드를 소스와 함께 보여주는 디어셈블러
JRE ( Java Runtime Environment )
자바 실행 환경, JRE 는 JVM 을 구현한다. Java 파일 실행을 위한 환경이며, JVM 이 실행에 필요한 라이브러리를 포함한다. 자바개발이 아닌 실행만을 목적으로 JRE 만 설치하여 사용할 수 있다.
자바로 만들어진 프로그램을 실행시키는데 필요한 라이브러리들과 각종 API, 그리고 자바 가상 머신 (JVM)이 포함되어 있다. JRE는 자바로 개발(쓰기)은 안되고 실행(읽기)만 된다고 보면 된다.
- JRE 는 JVM 의 실행환경을 구현했다.
=> JRE 는 JVM 의 실행환경이다.
=> JVM 은 JRE 를 통해 구현된다.
=> JVM 은 JRE 을 통해 작업한다.
- JRE 는 JVM 을 포함한다.
=> JRE 는 JVM 을 실행하기 위한 라이브러리를 포함한다.
=> JRE 는 JVM 이 Java 프로그램을 실행할 때 필요한 라이브러리를 포함한다.
JVM ( Java Virtual Machine )
자바 바이트 코드(Java Byte Code, *.class)를 해당 컴퓨터의 명령어로 해석해주는 역할하는 인터프리터(프로그램, 환경)이다. JAVA 를 플랫폼(매킨토시, 윈도우, 유닉스 등)에 제약없이 사용할 수 있도록 한다. JVM 은 플랫폼에 의존적이기 때문에 윈도우, 리눅스용이 다르다. 특징은 컴파일러와 다르게 자바명령을 한번에 읽고 실행하기 때문에 컴파일러보다 느리다는 점이다.
1. Class Loader
Java는 기본적으로 ‘Java Compiler’를 통해 컴파일을 통해 컴퓨터가 이해할 수 있는 바이트(Byte) 형식의 ‘.class’파일을 생성한다.
Class Loader는 이 ‘.class’파일들을 JVM 내의 메모리 영역(Runtime Data Area)에 올리고(load) 링크를 통해 배치하는 작업을 수행한다. 런 타임시 동적으로 클래스를 로드하고 jar 파일 내 저장된 클래스들을 JVM 위에 탑재한다.
즉, 클래스를 처음으로 참조할 때, 해당 클래스를 올려주고(loading) 검증하고(linking) 초기화하는(initialization) 역할을 한다. 이를 통해 IDE에서 프로그래밍한 파일들이 각 클래스가 유기적으로 동작할 수 있게 된다.
1) Loading
여러개의 로더를 사용하여 클래스간의 충돌을 막을 수 있고, 사용하지 않는 클래스를 로더에서 지움으로써 효율적 사용이 가능해진다.
- Bootstrap Loader - 가장 필수가 되는 Library class들을 로딩. (rt.jar) 다른 클래스로더의 부모격.
- Extension Loader - 그다음으로 중요한 class들을 로딩.
- Application Loader - 개발자가 작성한 class들을 로딩.
위(Bootstrap)에서 찾기를 실패하면 아래로 요청을 위임하는 형식이다. Application에도 없으면 ClassNotFoundException이다.
2) Linking
Verification(클래스파일이 옳바른지 검증)
Preparation(클래스, 인터페이스 등에 필요한 static field를 메모리 할당 및 기본값 초기화)
Resolution(Symbolic Refernece 주소값을 Method Area의 Direct Reference라는 메모리 주소값으로 변환)
과정이 있다.
3) Initialization
앞의 과정이 끝나면 클래스파일의 코드를 읽는다. 이때 클래스와 인터페이스 값들을 지정한 값으로 초기화하는 과정이다. 이 동작은 멀티스레드로 동작하기에 동시성을 고려해야한다.
2. Execution Engine
클래스를 실행시키는 역할이다.
클래스 로더가 JVM내의 런타임 데이터 영역에 바이트 코드를 배치시키고, 이것은 실행 엔진에 의해 실행된다.
자바 바이트 코드(*.class)는 기계가 바로 수행할 수 있는 언어(바이너리 코드)보다는 비교적 인간이 보기 편한 형태로 기술된 것이다.
그래서 실행 엔진은 이와 같은 바이트 코드를 실제로 JVM 내부에서 기계가 실행할 수 있는 형태로 변경한다.(기계어 번역 후 명령어 단위 실행)
Execution Engine 은 자바 바이트 코드를 명령어 단위로 읽어서 실행한다.(그래서 좀 느리다)
Execution Engine이 바이트코드를 명령어 단위로 읽어서 수행하는데 크게 두가지 방식이 있다.(Interpreter, JIT 컴파일러)
1) Interpreter
초기에 도입된 방식으로 바이트 코드를 한줄씩 읽어 기계어로 해석, 실행하는 방식이기에 반복문에서는 매번 이 작업을 반복해야하기에 성능이 별로다.
2) JIT(Just-In-Time) 컴파일러
위 방식의 속도문제를 해결하기 위한 방법이다. 모든 코드를 JIT을 통해 명령어로 변하는 것은 아니고, 위 방식을 사용하다가 컴파일 임계치라는 특정 사용기준을 넘기면 JIT 방식으로 실행하게 된다. 이때 컴파일한 코드는 캐싱을 하기에 비교적 빠르다.
3. Garbage Collector
흔히 GC라고 불리는 Garbage Collector 이다.
JVM 상에서 더 이상 사용되지 않는 데이터가 할당되어있는 메모리를 해제시켜주는 장치이다. JVM 에서 자동으로 동작하기 때문에 Java 는 특별한 경우가 아니면 메모리 관리를 개발자가 직접 해줄 필요가 없다. GC 가 주로 동작하는 대상은 Heap 영역 내의 객체 중에서 참조되지 않은 데이터이다. 이를 할당 해제해준다.
명시적으로 불필요한 데이터를 null로 처리하여 회수할 수도 있다.
힙 영역의 Young Generation에서 일어나는 GC를 Minor GC, Old Generation에서 일어나는 GC를 Major GC라고 한다. 일반적으로 Major GC는 Minor GC보다 오래걸린다.
Reachability와 Stop-the-world라는 중요한 개념이 있다.
1) Rechability
GC에서 객체가 Garbage인지 아닌지를 판별하는 개념이다. 어떤 객체로부터 유효한 참조가 있을경우 reachable, 아니면 unrechable 객체라고 한다.
유효한 참조를 하는 최초의 셋을 Root set이라고 한다. 즉 Root set으로부터 유효한 참조가 있냐 없냐가 Garbage인지 판별하는 중요한 요소가 된다.
Heap 영역 내부의 객체들이 java stack, native stack 등 외부에서 참조된다면 reachable인 것이다. 자기 자신이 누군가를 참조하지만 참조되는 경우가 없다면 그것은 unreachable 객체인 것이다.
2) Stop-the-world
GC를 수행하기 위해서 JVM이 애플리케이션 실행을 멈추는 것을 말한다.
이게 발생하면 GC 스레드를 제외한 나머지는 모두 처리 대기 상태가 된다. 어떤 GC 알고리즘도 결국 Stop-the-world가 발생한다. 그래서 GC 튜닝 기법을 통해 Stop-the-world 시간을 줄여 애플리케이션 성능을 높이는 것이 중요하다.
GC 기본 알고리즘
1) Mark & Swap Algorithm
GC의 가장 기본이 되는 알고리즘으로 Root set으로부터 참조된 객체를 마킹하는 Mark와 Mark가 없는 객체를 메모리에서 해제하는 Swap 단계가 있다.
객체를 메모리에서 해제하면서 객체가 위치해 있던 메모리부분이 정리되지않아 조각난 상태로 유지되는 단편화(fragmentation) 문제가 생기는 단점이 있다.
2) Mark & Compact Algorithm
위 방식에서 메모리를 정리하는 Compact 단계를 추가하여 framgmentation 문제를 해결한 알고리즘이다.
여러가지 GC 방식으로는
- Serial GC - 가장 기본 방식
- Parallel GC - 멀티스레드 방식
- CMS GC - Stop-the-world 최소화한 방식
- G1 GC - 가장 성능이 좋아 JDK 8 부터 기본 GC, CMS를 개선한 것으로 Stop-the-world시간이 가장 짧다. 힙 영역을 region이라는 단위로 나눠서 메모리를 관리한다. GC가 일어날때 전체 힙 영역을 탐색하는 것이 아닌 region 단위로 탐색하고 각 region 단위로 GC가 일어난다.
4. Runtime Data Area
프로그램을 수행하기 위해 OS에서 할당받은 메모리 공간이다.
JVM이 운영체제 위에서 실행되면서 할당받는 메모리 영역이다. 여러가지 세부 항목으로 나뉘어 지지만, 보통은 크게 3가지로 분류된다. (Method Area, Stack, Heap)
1) PC Register -
Thread가 시작될 때 생성되며 생성될 때마다 생성되는 공간으로, 스레드마다 하나씩 존재한다. Thread가 어떤 부분을 어떤 명령으로 실행해야할 지에 대한 기록을 하는 부분으로 현재 수행 중인 JVM 명령의 주소를 갖는다.
스레드별로 동시에 동작할 수 있도록 JVM 주소를 저장하는 것이다.
즉 현재 스레드가 실행되는 부분의 주소와 명령을 저장하고 있는 영역이다.
2) Stack Area -
프로그램 실행과정에서 임시로 할당되었다가 메소드를 빠져나가면 바로 소멸되는 특성의 데이터를 저장하기 위한 영역이다.
각종 형태의 변수나 임시 데이터, 스레드나 메소드의 정보를 저장한다.
메소드 호출 시마다 각각의 스택 프레임(그 메서드만을 위한 공간)이 생성된다. 메서드 수행이 끝나면 프레임 별로 삭제를 한다. 또한 메소드 안에서 사용되는 값들을 저장한다. 또 호출된 메소드의 매개변수, 지역변수, 리턴 값 및 연산 시 일어나는 값들을 임시로 저장한다.
호출 스택의 최상단에 위치하는 메서드가 현재 실행 중인 메서드이며, 나머지는 대기상태로 있는다.
* 프레임(frames) : method 가 호출될 때 생성되고 method 호출이 끝날 때 제거되며, stack 의 내부에 있다. 결과의 일부, 데이터, 동적인 linking(참조변수의 값), method 의 return 값을 저장하는 공간이다. (저장단위)
** 메소드 저장 **
3) Native Method Area -
자바 프로그램이 컴파일되어 생성되는 바이트 코드가 아닌 실제 실행할 수 있는 기계어로 작성된 프로그램을 실행시키는 영역이다. JAVA가 아닌 다른 언어로 작성된 코드를 위한 공간이다.
Java Native Interface를 통해 바이트 코드로 전환하여 저장하게 된다.
일반 프로그램처럼 커널이 스택을 잡아 독자적으로 프로그램을 실행시키는 영역이다.
4) Heap Area(모든 스레드 공유 = GC의 대상) -
JVM 이 실행될 때 생성된다.
모든 클래스 인스턴스나 배열이 위치해 있는 run-time data 공간이다. 객체(인스턴스)가 생성되는 공간, 프로그램 실행 중 생성되는 모든 객체는 이곳에 생성된다.(동적)
그림과 같이 세 영역으로 구분된다.
- Permanent Generation(PerGen) - 생성된 객체들의 정보의 주소값이 저장된 공간이다. 클래스 로더에 의해 load되는 Class, Method 등에 대한 Meta 정보가 저장되는 영역이고 JVM에 의해 사용된다.
Reflection을 사용하여 동적으로 클래스가 로딩되는 경우에 사용된다. - New/Young Generation - 이곳의 인스턴스들은 추후 가비지 콜렉터에 의해 사라진다. 생명 주기가 짧은 “젊은 객체”를 GC 대상으로 하는 영역이다.(Eden: 객체들이 최초로 생성되는 공간, Survivor 0, 1: Eden에서 참조되는 객체들이 저장되는 공간) Eden 영역이 꽉 차면 새로운 객체를 생성할 수 없기 때문에 Minor GC가 발생한다. 사용되지 않는 메모리는 해제하고 Eden영역에 있던 객체는 Survivor로넘어가게 된다. Survivor 0,1 중 하나는 반드시 비어있어야 한다.
- Tenured(Old) Generation - 이곳의 인스턴스들은 추후 가비지 콜렉터에 의해 사라진다. 생명 주기가 긴 “오래된 객체”를 GC 대상으로 하는 영역이다. New/Young Area에서 일정시간 참조되고 있는, 살아남은 객체들이 저장되는 공간이다. (Major GC)
Young에서 Old로 옮겨가는 것을 promotion이라고 한다.
해당 영역이 모두 차게되면 OutOfMemoryException이 발생한다.
** 인스턴스 저장 **
5) Method Area(모든 스레드 공유 = GC의 대상)(= Class Area = Static area) -
클래스 정보를 처음 메모리 공간에 올릴 때 초기화되는 대상을 저장하기 위한 메모리 공간이다.
JVM이 구동될 때 생성되며 JVM이 멈추게 되면 해당 영역도 소멸한다.
JVM이 읽어들인 각각 클래스의 run-time 상수(final), method data, method, 특별한 method(instance 나 interface 의 초기화), 클래스 메타데이터와 같은 것을 저장한다.
'static' 으로 선언된 method, 변수의 경우, JVM이 시작된 후 Method Area 에 올라간다.(main method) 호출하지 않고 공통으로 사용하는 함수이다.
※ Runtime Constant Pool
스태틱 영역에 존재하는 별도의 관리영역이다.
상수 자료형을 저장하여 참조하고 중복을 막는 역할을 수행한다.
** 클래스 저장 **
** 자바 프로그램의 흐름 **
- 개발자/운영자가 자바 소스를 작성한다.(*.java)
- Java Compiler(컴파일러)를 통해 소스파일을 컴파일 한다.(javac 사용)
- 컴파일 된 코드(자바 바이트 코드 : Java Byte Code, *.class)를 JVM이 읽을 수 있는 파일(*.class)을 생성한다.
- JVM은 자바 바이트 코드(Java Byte Code, *.class)를 읽어 실행한다.
- JVM은 어플리케이션 실행 중에 지속적으로 메모리를 관리한다.