티스토리 뷰

JVM이란 무엇인가?

Write Once, Run Anywhere

 자바의 장점을 얘기하면 가장 먼저 나오는 말인 "운영체제에 독립적이다"를 대표해주는 가상 머신이다.

 

 기존 언어들이 운영체제에 맞게 따로 개발을 해야하는 것에 비해 자바는 운영체제에 독립적으로 움직이며 이를 JVM(Java Virtual Machine, 자바 가상 머신)이 도와준다. 어떤 운영체제(Window, Mac, Linux, ...)에서 자바를 실행하건 JVM이 그 사이에 끼여서 작동한다고 보면 된다.

(마치 내가 어떤 나라에 가건 사용할 수 있게 해주는 돼지코 같은 느낌이다.)

 

 직역하면 "자바를 실행하기 위한 가상 컴퓨터" 이며 이때, 가상 기계(Virtual Machine)는 소프트웨어로 구현된 하드웨어를 뜻하는 넓은 의미이다. 즉 JVM은 실제 하드웨어가 아닌 소프트웨어로 구현된 컴퓨터로 컴퓨터 속의 컴퓨터 이다.


(출처 : https://docsplayer.org/118032581-Jvm-%EB%A9%94%EB%AA%A8%EB%A6%AC%EA%B5%AC%EC%A1%B0.html)

 

 

 위에서 언급한대로 JVM에서 어떤 운영체제에서도 자바를 사용할 수 있게 Java code에서 넘어온 ByteCode를 기계어로 번역해주는 과정(Interpret)을 거치기 때문에 속도가 느리다. (JIT Compiler와 향상된 최적화 기술로 어느정도 속도가 느리다는 단점이 완화되었다고한다.)

 

컴파일 하는 방법

.java를 .class로 변환하는 것

 우리가 IDE에서 코딩해서 만드는 Java Code(.java)는 사람이 알아보기 쉽게 만든 언어로 이걸 실행하기 위해서는 JVM이 읽을 수 있도록 ByteCode(.class)로 만들어줘야 한다.

 

 ByteCode로 변환하는 과정은 Java Compiler(javac)가 진행을 하며 javac는 JDK(Java Development Kit, 자바 개발 도구)안에 포함되어있다.

 이처럼 처음 Week1이라는 package안에는 hello.java 파일 밖에 없었지만 Java Compiler(javac)를 사용했을 때 hello.class라는 ByteCode가 생성되는 것을 확인할 수 있다.

 

자바 실행하는 법

 Java Compiler를 통해 컴파일된 ByteCode(.class)를 JVM에 넣으면 실행된다.

 

 JVM에서는 ByteCode를 Class Loder - Runtime Data Area - Execution Engine 순으로 이동시켜 실행하는데 자세한 내용은 아래에 JVM 구조에서 다룬다.

 실행도 정말 간단하다. java 명령어를 통해 컴파일한 파일을 실행하면된다.

IDE에서는 컴파일하는 일련의 과정 없이 Java Compiler를 자동으로 사용해서 제공하기때문에 Run버튼 하나로 만능이다...!

 

 

서로 다른 버전의 Java Compiler를 사용한다면 어떻게 될까?

백기선님의 스터디 라이브에서 백기선님이 질문해주신 내용이다.

 

 사실 서로 다른 버전이어서 Java Compiler 버전도 다르면 둘다 충돌이 일어날 것이라고 생각했지만 결과는 상위버전에서 컴파일한 ByteCode는 하위버전에서 불가능하고 하위버전에서 컴파일한 ByteCode는 상위버전에서 실행된다.

 에러메시지를 잘 보면 55.0버전(jdk 11)으로 컴파일 되었지만 Java Runtime Version은 '52.0 버전(jdk 8)'에서만 가능하다고 설명한다.

 

 결국 우리가 많이 사용하는 java 8 개발환경에서 개발할 때는 생각없이 사용했던 개발 환경이 java 11로 개발해야하는 상황이 되면 고려해야할 점이 생기게 되는 것이다.

 

 javac에서는 이러한 에러를 막기 위해서 상위 버전의 jdk를 사용하더라도 하위 버전에서 실행할 수 있도록 ByteCode로 컴파일할 수 있는(-target) 다양한 옵션를 제공한다.

option document
-classpath(cp) (path) 컴파일하기 위해서 필요로하는 참조할 클래스 파일을 찾기 위해서 컴파일 시 참조할 파일 경로를 지정해주는 옵션
-d (directory) 클래스 파일을 생성할 루트 디렉토리를 지정해주는 옵션(default : 소스파일이 위치한 파일)
-target (version) 입력된 version에도 호환이 될 수 있도록 클래스 파일을 생성하게 해주는 옵션
... ...

출처 : 공식 문서는 다음에 사용하게될 기회가 되었을 때 공부해야할 것 같다...

 

바이트코드란 무엇인가

JVM이 사용하는 중간 언어

 꾸준히 사용하게 될 Java Code는 사람이 읽기 쉽게 만든 언어이므로 컴퓨터는 이해할 수 없어서 컴퓨터가 알 수 있게(기계어) 번역해야한다.

 

 기계어로 번역하는 과정을 JVM이 하는데 그렇다고 JVM도 Java Code는 읽을 수 없고 Java Code를 JVM이 읽을 수 있도록 번역한 것이 ByteCode이다.

 

 

 위의 컴파일 과정에서 Java Compiler를 통해 ByteCode가 생성되고 이 ByteCode를 JVM이 읽어들어 기계어로 번역한다.

 (자바 컴파일러에 의해 변환된 코드의 명령어의 크기가 1 Byte라서 ByteCode라고 불린다고한다.)

 

JIT 컴파일러란 무엇이며 어떻게 동작하는지

 JIT(Just-In-time) Compiler는 JVM에서 ByteCode를 실행하기 전 기계어로 번역하는 과정에서 사용된다.

 

JIT 컴파일러를 알기 전 기본적인 프로그램 컴파일 방법에 대해서 먼저 알아보면,

 

1. Dynamic Compile(정적 컴파일)

 Runtime을 시작하기 앞서서 받은 Code를 전부 기계어로 해석하고 실행하는 방법이다. Runtime 전에는 변환하는 과정때문에 구동 시간이 걸리지만 구동된 이후에는 하나의 패키지로 빠르게 동작한다는 장점이 있다.

2. InterPret Compile(인터프리트 컴파일)

 한 줄씩 명령어 단위로 읽고 실행하는 컴파일 방법이다. Runtime 전에 한꺼번에 컴파일하는 정적 컴파일 방식과는 다르게 읽으면서 컴파일하기 때문에 느리다는 단점이 있다.

 

 

 JIT 컴파일러는 두 컴파일 방식을 혼합한 컴파일러로 Runtime 시점에서 인터프리트 방식으로 기계어를 생성하여 그 코드를 캐싱해둔 뒤, 다시 반복해서 호출됐을 때 또 생성하는 것을 방지한다.

 

 최근의 JVM버전에서는 JIT 컴파일러를 지원해서 인터프리트의 느리다는 단점을 해소했다.

(자바가 느리다는 고전적인 단점이 점점 사라져간다고 생각한다.)

 

JVM 구성요소

JVM은 크게 총 3가지로 이루어져 있다.

  1. Class Loader
  2. Runtime Data Area
  3. Execution Engine

 

1. Class Loader

 Java Compiler가 변환해준 ByteCode(.class)들을 모아서 JVM의 메모리 영역인 Runtime Data Area에 저장하는 역할을 한다.

 

 여러개의 자바 클래스들이 JAR(Java Archive, 자바 파일 포맷)으로 묶여 올라가는데 모든 클래스가 한번에 메모리에 올라가지 않고 각 클래스가 필요할 때 어플리케이션에 올려준다.

2. Runtime Data Area

 JVM이 사용하도록 OS가 할당해준 컴파일된 자바 클래스파일을 저장하는 메모리 공간이다.


 목적에 따라 5가지 영역으로 구성되어 있다.

구분 Areas docs
모든 쓰레드가 공유 Method Area 클래스가 사용되면 클래스파일을 읽어서 분석하고 분석된 클래스, 인터페이스, 메소드, 필드, static 변수 등의 ByteCode 등을 저장
Heap Area 사용자가 관리하는 인스턴스가 생성되는 공간으로 객체를 동적으로 생성하면 인스턴스가 Heap 메모리에 할당되어 사용됨, Garbage Collection의 대상이 되는 영역
쓰레드가 별도 생성 PC Register CPU가 Instruction을 수행하는 동안 필요한 정보를 저장
Stack Area 쓰레드가 시작될 때 생성되며 Method가 호출될 때 Method와 Method 정보를 저장하고 호출이 종료될 때 제거
Native Method Stack Java 이외의 언어로 작성된 native 코드 정보를 저장

 모든 쓰레드가 공유하는 메모리 영역은 JVM이 시작될 때 실행되고 JVM이 종료되면 해제가 된다.

 

3. Execution Engine

 메모리에 저장된 ByteCode를 직접 실행하는 역할을 한다.

 

 실행 엔진은 크게 Grabage Collection과 JIT Compiler가 있는데 JIT Compiler는 위에서 설명했고 GC에 대해 이야기 해보고자 한다. GC는 다음의 두 가지 명제에 의해서 제작하게 되었다고 한다.

 

  • 대부분의 객체는 Unreachable(참조되지않는) 상태가 된다.
  • 오래된 객체에서 젊은 객체로의 참조는 드물게 존재한다.

 메모리가 무한하다면 메모리를 굳이 정리할 필요가 없지만 메모리의 Heap 영역은 그 용량이 정해져 있기 때문에 더이상 참조되지 않는 객체를 정리해준다. (메모리에 사용하지 않는 값을 저장해 둔다면 메모리 누수가 발생된다.)

 

 C나 C++에서는 malloc 같은 함수로 사용자가 직접 메모리에 값을 할당하고 정리해줘야 했지만 GC는 사용자가 하는 것이 아닌 JVM이 알아서 진행 해준다. (매우 편리하다!)

 

 

 System.gc()메소드를 통해서 개발자가 직접 실행은 가능한데 GC가 동작한다는 보장은 없다고 한다. 참조되지 않는 객체들을 반환해서 메모리 공간을 재배치를 진행할 뿐 실제 처리를 하지 않을 수 있기 때문이다. (결국 System.gc() 메소드는 시스템 성능에 큰 영향을 끼치지만 리턴되는 것이 없기 때문에 사용을 지양해야한다.)

 

JDK와 JRE의 차이

 JDK(Java Development Kit, 자바 개발 도구) , JRE(Java Runtime Environment, 자바 실행 환경)은 사용목적으로 명확하게 구분할 수 있다.

 

JRE는 단순히 자바를 실행하기 위한 환경으로 Java Code를 받아서 실행하는 역할을 하고 JDK는 Java Code를 제작하기 위한 환경을 제공하는 역할을 한다. Java 프로그램을 외주로 맡긴다고 치면 개발자는 JDK를 Java를 개발하고 JRE에서 잘 개발했는 지 확인하며 외주받은 사람은 JDK없이 JRE에서 잘돌아가는 프로그램을 받으면 된다고 생각하면 간단할 것 같다.

 

JRE는 이미 JDK를 다운받았을 때 JDK에 내장되어서 설치가 되기 때문에 보통 개발 환경을 세팅할 때 신경쓰지 않아도 된다.

굳이 이 JDK에도 JRE가 담겨져 배포되는데 굳이 이걸 구분을 해야하나 싶었는데 Java 9부터는 JRE만 따로 배포하지 않고 JDK에 모두 통합 배포된다고 한다!

'JAVA > whiteship-livestudy' 카테고리의 다른 글

상속  (0) 2021.03.28
클래스  (0) 2021.03.24
제어문  (0) 2021.03.17
연산자  (0) 2021.03.16
자바의 데이터 타입, 변수 그리고 배열  (0) 2021.03.15
댓글
최근에 올라온 글
Total
Today
Yesterday