Java

[JAVA] JVM

📝 작성 : 2021.02.07  ⏱ 수정 : 
728x90

JVM(Java Virtual Machine)

JVM이란?

Java의 가장 큰 특징 중 하나는 OS(Operating system)에 독립적이라는 것 입니다.
먼저, 개발자가 .java파일을 생성합니다. 이를 Java Compiler의 javac라는 명령어를 이용해 컴파일하여 .class파일을 생성합니다.
.class파일은 Bytecode로 되어있는데 이는 아직 컴퓨터가 읽을 수 없습니다. JVM은 Bytecode를 OS가 이해할 수 있도록 해석해줍니다.
JVM이 Bytecode를 해석하는 단계가 추가되어 속도가 느렸지만 JIT(Just In Time) 컴파일러를 통해 이러한 점을 극복했습니다.

JVM의 구성요소

JVM의 구성요소는 크게 Class Loader, Runtime Data Area, Excution Engine으로 구성되어있습니다.

Class Loader

RunTime시점에 JVM이 OS로 부터 할당받은 메모리영역인 Runtime Data Area로 클래스 파일을 로딩하는 역할을 합니다.


클래스 로딩 과정
  1. 처음 보는 메서드 발견시 JRE Libraries 폴더에서 해당 메서드를 가진 클래스를 찾음
  2. classpath 환경변수에 지정된 폴더에서 찾음
  3. 올바른 파일인지 Bytecode검증
  4. 메서드영역으로 파일을 로딩
  5. JVM이 종료될때 까지 유지

Runtime Data Area

JVM이 프로그램을 실행하기 위해 할당받은 메모리 공간입니다. 크게 5가지 영역으로 구분할 수 있습니다.

Method Area

모든 쓰레드가 공유하는 메모리 영역입니다. 클래스, 인터페이스, 메서드, 필드, Constant pool, final, static 변수 등의 내용을 보관합니다.

Heap Area

객체 정보를 보관합니다. GC(Garbage Collector)의 대상입니다.

Stack Area

메서드가 호출되면 메서드와 메서드 정보를 이곳에 보관합니다. 메서드 호출이 종료되면 Stack point에서 제거됩니다. 쓰레드가 시작될 때 생성되며 각 쓰레드 별로 생성되기 때문에 다른 쓰레드에서는 접근할 수 없습니다. Heap에 있는 객체가 Stack에서 참조 할 수 없는 경우 GC대상이 됩니다.

PC Register

현재 쓰레드가 실행되는 부분의 주소와 명령을 저장합니다.

Native Method Stack

JNI(Java Native Interface)를 통해 호출되는 C/C++ 등의 코드를 수행하기 위한 스택영역입니다.

Execution Engine

로드된 클래스의 Bytecode를 명령어 단위로 읽어서 실행합니다. 최초의 JVM은 Interpeeter방식(한 줄씩 해석하고 실행하는 방식)이였기 때문에 속도가 느렸습니다. 하지만 JIT(Just In Time) 컴파일러를 통해 이를 보완했습니다. JIT 컴파일러는 아래에 따로 기술헀습니다.

JIT(Just-In-Time) Complier

자바에서 인터프리터(Interpreter)는 바이트코드 명령어를 하나씩 읽고 실행합니다. 하나씩 해석하고 실행하기 때문에 하나하나의 해석은 빠르지만 인터프리팅 결과의 실행은 느립니다. 이러한 단점을 보완하기 위해 도입된 것이 JIT컴파일러입니다. 인터프리터 방식으로 실행하다 적절한 시점에 바이트코드 전체를 컴파일하여 네이티브 코드(기계어)로 변경합니다. 이후에는 해당 메서드를 더이상 인터프리팅하지 않고 네이티브 코드로 직접 실행합니다. 네이티브 코드는 캐시에 보관하기 때문에 한번 컴파일 된 코드는 계속 빠르게 수행할 수 있다는 장점이 있습니다. 하지만 만약에 한번만 실행되는 코드라면 오히려 바이트코드를 컴파일하는 JIT컴파일 방식이 더 비효율적일 수 있습니다. 따라서 JIT컴파일러를 사용하는 JVM은 내부적으로 해당 메서드의 빈도를 체크하여 컴파일을 수행합니다.

컴파일 하는 방법(터미널을 통한)

javac 명령어를 통해 컴파일 할 수 있습니다. javac <options> <source files> 이런 형태로 사용합니다.

컴파일 옵션

  1. -classpath(-cp) : 컴파일 시 필요한 라이브러리나 클래스 파일들의 경로를 지정
  2. -d : 클래스 파일을 생성할 루트 디렉토리 지정
  3. -encoding: 소스 파일의 인코딩 정보(UTF-8, EUC-KR 등)을 설정합니다. 명시하지 않으면 플랫폼의 기본값으로 설정됩니다.
  4. -g : 모든 디버깅 정보를 생성합니다. -g{lines,vars,soucre}와 같이 생성할 디버깅정보를 특정 지을 수 있고 -g:none와 같이 디버깅 정보를 생성하지 않을 수도 있습니다.
  5. -nowarn : 경고메시지를 생성하지 않습니다.
  6. -sourcepath : 소스코드의 위치를 지정
  7. -target : 특정 버전의 JVM에서 작동 할 수 있도록 버전을 지정합니다.

터미널에서 javac명령어를 입력하거나 Oracle에서 제공하는 문서에서 더 많은 옵션들을 확인할 수 있습니다.

자바 파일 실행

정확하게는 자바 소스파일을 컴파일한 클래스파일을 실행하는 것입니다.
java명령어를 통해 클래스 파일을 실행할 수 있습니다. 여기서 주의할 점은 컴파일시에는 javac Hello.java로 확장자까지 써주어야 하지만 클래스파일 실행시에는 java Hello로 확장자를 써주지 않습니다.

javac와 마찬가지로 다양한 옵션들이 있으며 터미널에서 java명령어를 입력하거나 Oracle에서 제공하는 문서에서 다양한 옵션들을 확인할 수 있습니다.

Bytecode

바이트코드는 가상 컴퓨터에서 돌아가는 이진 표현법입니다. 하드웨어가 아닌 소프트웨어에 의해 처리되기 때문에 기계어보다 추상적입니다.
1Byte크기의 명령 코드(opcode)였기에 바이트코드라 불리게 되었습니다.
터미널에서 javap -c <class file> 명령어를 사용하거나 IntelliJ에서 class파일 실행 후 View > Show Bytecode 메뉴를 통해 바이트코드를 확인할 수 있습니다.

간단하게 바이트코드가 어떻게 생겼는지만 알아보겠습니다.

이 파일을 컴파일 하면

이런 바이트코드가 생성됩니다.

바이트코드의 명령어는 이 곳에서 확인할 수 있습니다.

JRE vs JDK

JRE(Java Runtime Environment)는 자바 실행환경의 약자입니다. 당연히 JVM을 포함하고 있고 그 외에 필요한 라이브러리를 갖고있습니다.

JDK(Java Development Kit)는 자바 개발도구의 약자입니다. 자바 개발을 위해 필요한 도구(javac, java등)과 만들어진 자바 프로그램을 실행하기 위한 환경 JRE를 포함하고 있습니다.

반응형