[Java]Day01 - JVM이란?
2021-01-19 # Java

1. JVM이란?

JVM의 용도와 정의


JVM에는 2가지 기본 기능이 있다.
첫번째로 자바 프로그램이 어느 기기, 또는 어느 운영체제 상에서도 실행될 수 있게 하는 것과 프로그램 메모리를 관리하고 최적화하는 것이다. 이에 대해서는 JVM 구성요소에서 자세히 다뤄보겠다.
가장 유명한 원칙인 “한 번 작성해, 어디에서나 실행한다.(Write Once, Run Anywhere, WORA)”가 바로 이것이다. 즉, 자바 애플리케이션은 JVM 위에서 동작하기 때문에 JVM에 종속적이나, OS와 하드웨어와는 독립적이라 프로그램의 변경없이 실행이 가능하다.
자바가 나온 시점에는 메모리관리와 OS와 하드웨어 의존성이 있었기 때문에 이는 큰 장점이기도 했다.

1
2
- 기술적 정의 : JVM은 코드를 실행하고 해당 코드에 대해 런타임 환경을 제공하는 소프트웨어 프로그램에 대한 사양(Specification)이다.
- 일반적 정의 : JVM은 자바 프로그램을 실행하는 방법이다. JVM의 설정을 구성한 다음 설정사항에 따라 실행 중에 프로그램 리소스를 관리한다.

Garbage Collection

자바 이전에는 프로그래머가 모든 프로그램 메모리를 관리했었다. 자바에서는 JVM이 프로그램 메모리를 관리한다. JVM은 가비지 컬렉션이란 프로세스를 통해 메모리를 관리하며, 이는 프로그램에서 사용되지 않는 메모리를 지속적으로 찾아내서 제거한다. 즉, 실행중인 JVM내부에서 일어난다.
그럼 왜 많은 개발자들이 C나 C++같이 메모리관리를 직접해야하는 언어를 사용하냐는 생각이 들 수 있다. 하지만 편리함에는 대가를 치루는 법. 앞서 말했듯 JVM이 지속적으로 가비지 컬렉션을 하기 때문에 시간이 그만큼 많이 들어간다. 하지만, 직접관리를 한다면 그 시간을 줄일 수 있을 것이다. 그래서 초창기 자바의 가비지컬렉션에 대해 속도측면에서 많이 까였다. 하지만, 지금은 많은 개발과 최적화를 통해 크게 개선되어 메탈에 가까워졌다.


컴파일 하는 방법

컴파일 하는 과정
위 링크에 컴파일 하는 과정을 잘 요약해주었다.

컴파일 하는 과정은 다음과 같다.

1
2
3
1. 소스코드를 작성한다.(.java)
2. 컴파일러를 통해 바이트코드로 컴파일한다. (.class)
3. java명령어로 프로그램을 실행한다.

대부분의 나같은 주니어개발자들은 intelliJ나 이클립스같은 IDE를 통해 컴파일부터 실행까지 모두 하지만 위의 과정들은 당연히 알아두어야한다.


바이트코드란?

앞선 과정중에 바이트코드라는 것이나온다. 보통 C나 C++을 배우다보면 이런 것은 들어본 적이 없다. 바이트 코드의 정의는 다음과 같다.

바이트코드(Bytecode, portable code, p-code)는 특정 하드웨어가 아닌 가상 컴퓨터에서 돌아가는 실행 프로그램을 위한 이진 표현법이다. 하드웨어가 아닌 소프트웨어에 의해 처리되기 때문에, 보통 기계어보다 더 추상적이다.
다음의 자바코드가 있다고 하자.

1
2
3
4
5
6
7
8
outer:
for (int i = 2; i < 1000; i++) {
for (int j = 2; j < i; j++) {
if (i % j == 0)
continue outer;
}
System.out.println (i);
}

이는 다음과 같은 바이트코드로 번역된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
0:   iconst_2
1: istore_1
2: iload_1
3: sipush 1000
6: if_icmpge 44
9: iconst_2
10: istore_2
11: iload_2
12: iload_1
13: if_icmpge 31
16: iload_1
17: iload_2
18: irem
19: ifne 25
22: goto 38
25: iinc 2, 1
28: goto 11
31: getstatic #84; // Field java/lang/System.out:Ljava/io/PrintStream;
34: iload_1
35: invokevirtual #85; // Method java/io/PrintStream.println:(I)V
38: iinc 1, 1
41: goto 2
44: return

바이트코드도 컴파일의 과정이므로 당연히 사람이 읽기 쉽도록 쓰인 소스코드보다 덜 추상적이며, 더 간결하고, 더 컴퓨터 중심적이다.(어셈블리어와 비슷한형태다.) 실은 아까 보여준 컴파일 과정중에 한가지 과정이 생략되었는데, 바로 실행전에 JIT컴파일러로 기계코드로 바꾸는 것이다. 이에 대해서는 다음 과정에서 살펴보겠다.


JIT 컴파일러란?

정의는 다음과 같다.

JIT 컴파일(just-in-time compilation) 또는 동적 번역(dynamic translation)은 프로그램을 실제 실행하는 시점에 기계어로 번역하는 컴파일 기법이다.

이런 기계어 변환은 코드가 실행되는 과정에 실시간으로 일어나며(그래서 Just-In-Time이다), 전체 코드의 필요한 부분만 변환한다. 기계어로 변환된 코드는 캐시에 저장되기 때문에 재사용시 컴파일을 다시 할 필요가 없다.

일반적인 인터프러터 언어(예시: cpython)는 바이트코드나 소스코드를 최적화 과정이 없기 번역하기 때문에 성능이 낮다. 반면 정적으로 컴파일하는 언어(예시: c 언어)는 실행 전에 무조건 컴파일을 해야하기 때문에 다양한 플랫폼에 맞게 컴파일을 하려면 시간이 오래 걸린다. 동적 컴파일 환경은 실행 과정에서 컴파일을 할 수 있기 위해 만들어졌다. JIT는 정적 컴파일러 만큼 빠르면서 인터프러터 언어의 빠른 응답속도를 추구하기 위해 사용한다. 바이트코드 컴파일러가 시간이 많이 소요되는 최적화를 미리 해주기 때문에 바이트코드에서 기계어 번역은 훨씬 빠르게 진행될 수 있다. 또한 바이트코드는 이식성이 뛰어나 가상 머신이 설치되어 있으면 빠르게 실행할 수 있다.


JVM 구성요소

JVM에는 3가지 측면이 있다고 할 수 있다. 표준(Specification), 구현(Implementation ) 그리고 인스턴스(Instance)인데, 각각에 대해 살펴보자.

1. 표준(Specification)

첫째, JVM은 소프트웨어 사양이다. 다소 순환적인 방식으로, JVM 사양은 구현에 있어 최대한의 창조성을 허용하기 위해, JVM 구현 세부사항이 사양 안에 정의되어 있지 않다고 강조하고 있다.
결국, JVM이 해야만 하는 일은 자바 프로그램을 정확하게 실행하는 것뿐이다. 간단해 보인다, 심지어 겉으로 보기에는 단순해 보이기도 하지만, 자바 언어의 능력과 유연성을 고려할 때, 이것은 엄청나게 힘든 일이다.

2. 구현(Implementation)

JVM 사양 구현은 실제 소프트웨어 프로그램을 도출하며, 이것이 JVM 구현이다. 실제로, 오픈소스와 특정 업체 고유의 JVM 구현이 다수 존재한다. 오픈JDK의 핫스팟(HotSpot) JVM은 참조 구현이며, 세계에서 가장 철저하게 증명된 코드기반 중 하나로 남아있다. 핫스팟은 가장 널리 사용되는 JVM이기도 하다.

오라클의 라이선스가 부여된 JDK를 포함해, 라이선스가 부여되는 거의 모든 JVM은 오픈JDK와 핫스팟 JVM의 포크(Fork)로 생성된 것이다. 오픈JDK로부터 허가받은 포크를 생성하는 개발자들은 종종 운영체제 고유의 성능 개선사항들을 추가하려는 욕구에 의해 동기 부여된다. 일반적으로, 개발자는 JRE(Java Runtime Environment) 번들의 한 부분으로 JVM을 다운로드해 설치한다.

3. 인스턴스(Instance)

JVM 스펙이 구현돼서 소프트웨어 제품으로 릴리즈되면, 개발자는 그것을 하나의 프로그램처럼 다운로드해 실행할 수 있다. 이렇게 다운로드 된 프로그램이 하나의 JVM 인스턴스(또는 인스턴스화된 버전)이다.

개발자들이 “JVM”에 대해 말하는 경우, 대부분의 경우에는 소프트웨어 개발 환경 또는 제품화 환경에서 실행되는 하나의 JVM 인스턴스를 지칭한다. “아난드, 그 서버에 있는 JVM은 메모리를 얼마나 사용하고 있어?” 또는 “순환 호출(Circular Call)을 하는 바람에 스택 오버플로우 에러가 내 JVM을 망가뜨렸다니, 믿을 수가 없군. 이런 초보적인 실수를 하다니!”라고 말할 지도 모른다.


JDK와 JRE의 차이

간단하게 설명하면 다음과 같다.

JRE란?
먼저 JRE는 Java Runtime Environment의 약자로 자바 프로그램을 실행시켜주는 환경을 구성해주는 도구다. 즉 JAVA를 개발할 필요는 없는데, 실행은 시켜줘야 하는 경우에는 꼭 JRE가 있어야 한다. 반면 JAVA 개발시 꼭 필요한 것이 있는데 그것이 바로 JDK다.

JDK란?
JDK는 Java Development Kit의 약자로 말그대로 자바 개발시 필요한 툴킷을 제공하는 도구모음이다. 개발하려면 당연히 실행도 시켜야 하므로 JDK 안에는 JRE가 포함되어 있다.

사용자 입장에서 요약하자면
JAVA로 만들어진 프로그램을 실행만 시킬 것이라면 JRE만 설치하면 되고
JAVA 개발자라면 JDK를 설치하면 된다.