본문 바로가기

Java Virtual Machine (JVM) & 자바 컴파일

자바 가상 머신 ( JVM )

 자바로 작성된 애플리케이션이 운영체제에 영향 받지 않고 동작할 수 있는 환경을 제공해준다.

Java로 프로그램을 작성하면 .java 라는 확장자로 저장된다. 자바 컴파일러는 .java 를 컴파일해서 .class 라는 확장자를 가진 바이트코드로 변환한다.

 

.class 파일은 JVM 위에서 작동이 된다.

 

OS의 종류에 상관없이 JVM이 설치되고 실행될수 만 있다면 Java로 작성한 프로그램은 어디에서나 실행 가능하다. 그러므로 다른 언어에 비해 이식성이 높다.


Jave Runtime Environment (JRE)

JVM은 .class 파일이 실행 될 수 있는 환경을 제공해 주지만 몇가지 필수적인 요소들이 더 필요하다. 그 중 하나가 JRE 이다.

 

JRE는 JVM, Java Class Libraries, Class Loader 로 구성이된다.

JVM위에서 실행되는 바이트코드는 필 수 라이브러리를 참고하므로 Java Class Libraries가 필요하다.

Class Loader는 필요한 클래스를 JVM 위로 올려준다.

 

JRE는 개발이 아닌, 실행에 초점을 맞추고 있다. 그래서 실제 자바 코드가 주어져도 컴파일 할 수 없다.


Jave Development Kit (JDK)

Java를 이용해서 개발 할 때 필요한 도구 모음이다. 개발 시 실행도 시켜봐야 하므로 JRE가 포함되어 있다.

JRE이외에 컴파일러 (javac), 디버깅(jdb) 기능이 포함되어 있다.

 

기능 제공 및 환경에 따라 여러 플랫폼으로 나뉜다.

1. JAVA SE: 가장 기본적. 대부분 패키지 포함
2. JAVA EE: 현업에서 사용되는 API포함. JSP, 서블릿, JDBC, JNDI 등이 포함
3.JAVA ME: 모바일 기기에서사용되는 API포함. 스마트폰의 OS로 인해 현재는 거의 사장됨

 

* JDK 8/11

현재 가장 널리 쓰이고 있는 2가지 버전이다. 왜냐하면 둘 다 LTS이기 때문이다.

 


JVM 구조

출처 : https://www.geeksforgeeks.org/jvm-works-jvm-architecture/

Class Loader

Class Loader 는 여러가지 역할을 한다.

 

1. Loading

각 디렉터리에 흩어진 클래스 파일을 JVM의 메모리에 탑재한다. 각 클래스 파일이 개발자가 작성하 것인지, 기본 제공 클래스 파일인지와 같은 기준에 의해 3가지로 나뉜다.

 

(1) Bootstrap Class Loader

다른 모든 Class Loader의 부모이다. JVM 구동 위한 필수 라이브러리의 클래스 파일을 JVM에 탑재한다. 가장 상위 Class Loader 이므로 OS에 맞는 네이티브 코드로 작성되어 있다.

 

(2) Extensions Class Loader

 Bootstrap Class Loader 다음 우선순위를 갖는다. localdata, zipfs 등 표준 자바 클래스의 라이브러리를 JVM에 탑재한다.

이 라이브러리는 $JAVAHOME/jre/lib/ext_ 에 존재한다

 

(3) Application Class Loader (System Class Loader)

Classpath에 있는 클래스, 즉. 개발자가 작성한 클래스 파일을 JVM에 탑재한다. 개발자가 Class Loader를 직접 구현하면 Application Class Loader의 자식 형태의 Class Loader를 구현한다.

 

3가지 Class Loader를 거쳤음에도, Class 파일 찾지 못하면 ClassNotFoundException 예외 발생

 

2. Linking

ClassLoader에 의해 로드된 클래스 파일을 검증하고, 사용 할 수 있도록 만든다. Linking 과정은 3단계로 구성되어 있다.

 

(1) Vertification

클래스파일이 유효한지 확인한다. JVM 구동 조건대로 구현되지 않았으면 VerifyError 던진다.

 

(2) Preparation

클래스와 인터페이스에 필요한 Static Field 메모리 할당하고 기본값으로 초기화한다.초기화된 값은 Initialization 과정에서 코드에 작성된 초기값으로 변경된다. 즉, 이 과정에서는 코드 작동시키지 않는다.

 

(3) Resolution

Symbolic References 값을 JVM의 Method Area의 런타임 환경 풀을 통해 Direct Reference 라는 메모리 주소 값으로 변경한다. new와 instanceof가 영향을 받는다.

 

3. Initialization

클래스 파일의 코드를 읽으며 class와 interface의 값들을 작성한 값들로 초기화 하고, 초기화 메소드를 실행시킨다.

이 때, JVM은 멀티 쓰레딩으로 작동을 하기 때문에 동시성을 고려해 줘야 한다. 이 과정이 끝나면 클래스 파일 구동 준비가 끝난다.

 


JVM Memory (Runtime Data Area)

JVM의 메모리 영역은 크게 5가지로 나뉜다. 

  • Method Area ( or Static Area or Class Area or Code Area)
    인스턴스 생성위한 객체 구조, 생성자, 필드 등이 저장된다. 그리고 Runtime Constant Pool 과 Static 변수, 메소드 데이터, Class 데이터도 이곳에서 관리된다.

    이 영역은 JVM당 하나만 생성된다. 즉, JVM의 모든 쓰레드들이 하나의 Method Area를 공유한다.
    JVM의 다른 메모리 영역에서 이 구역에 접근하면 실제 물리 메모리주소로 변환해서 전달한다.
    JVM 시작시에 생성되며, 종료 할 때 까지 유지된다.

  • Heap
    JRE클래스 및 코드 실행을 위한 객체가 저장된다. String Pool과 실제 인스턴스, 배열 등이 저장이 된다.
    JVM 당 하나만 생성이 되고, 모든 자바 Stack 영역에서 참조되어 쓰레드 간 공유된다.
    공유되므로 동시성 문제가 발생할 수 있다. 즉, Thread Safe 하지 않다. 그래서 Heap 영역의 인스턴스를 사용하게 되면 Synchronized 블록 등을 사용해서 동시성을 지켜줘야 한다.
    만약 Heap 영역이 가득 차면 OutOfMemoryError 가 발생한다.
    Heap 영역은 GC의 주 대상이다. Java Stack 영역에 비해 속도가 조금 느리다.
    GC에 의해서 메모리가 관리된다.

  • JVM Language Stacks ( Stack )
    Thread 별로 할당되는 영역이다. Heap 영역보다는 빠르다.
    각 Thread는 메소드를 호출하면 Stack 에 Frame을 Push 하고, 메소드가 종료되면 Pop 한다.
    Frame은 지역변수, Operand Stack(계산 위한 공간), Constant Pool Reference 으로 이루어져 있다.
    각 Frame의 연산이 종료되면 상위 Frame에 결과값을 반환해준다.

    Stack 영역이 가득 차면 StackOverflowError 가 발생한다.

  • PC Registers
    Thread는 각자 동작하므로 명령어 주소 값을 저장할 공간이 필요하다. 각 Thread들은 PC Registers 공간을 가지고 있어 Native 하지 않은 메소드의 주소값을 PC Registers에 저장한다.
    Native 하지 않으면 undefined가 기록된다.

 

  • Native Method Stacks (or C Stacks)
    Java가 아닌 다른 프로그램밍 언어로 작성된 메소드를 Native Method라고 한다. 이 영역에는 그러한 메소드들이 저장된다. Native Method가 실행 될 경우 이 Stack에 해당 메소드가 쌓인다. Thread 별로 Native Method Stacks를 가지고 있다.

 


JAVA 컴파일

  1.  .java 파일을 작성한다.
  2. 자바 컴파일러(javac) 가 .java 파일을 바이트 코드 (.class) 로 컴파일 한다. 바이트 코드는 아직 컴퓨터가 읽을 수 없고 JVM이 읽을 수 있다.
  3. 바이트 코드를 JVM의 Class Loader에게 전달한다.
  4. Class Loader는 Dynamic Loading 을 통해 필요한 클래스들을 JVM의 Runtime Data Area(JVM 메모리) 에 올린다.
  5. 실행 엔진은 JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 실행한다. 이 때 2가지 방식으로 실행한다
     5-1  인터프리터 : 바이트 코드명령어를 하나씩 읽고 실행 : 하나 실행은 빠르나 전체적인 실행은 느림
     5-2 JIT 컴파일러 : 인터프리터의 단점 보완하기 위해 도입. 바이트 코드를 바이너리 코드로 변경하고, 바이너리 코드          실행한다. 전체적인 실행은 인터프리터 보다 빠르다.

 

 

참고

https://johngrib.github.io/wiki/jvm-stack/
https://www.geeksforgeeks.org/jvm-works-jvm-architecture/

 

 

'JAVA > JAVA' 카테고리의 다른 글

Synchronized (동기화) in Java  (0) 2024.01.14
Garbage Collection - 가비지 컬렉션  (0) 2022.06.23
객체지향 & 절차지향 언어  (0) 2022.06.22
JAVA - 특징  (0) 2022.06.20