JVM 아키텍처

bo04·2024년 2월 23일
0

Java

목록 보기
1/3

출처

자바 vs 다른 언어

  • 자바 개발 전 C/C++를 사용할때: 각 하드웨어에 맞는 소스 코드를 여러개 작성해야되는 번거로움이 발생함(같은 기능의 소스코드인데도 달라야됨..)

  • 자바 개발 후: 각 OS에 맞는 JVM만 있으면, 같은 기능을 구현한 소스코드가 하나만 있어도 OS 상관없이 사용할 수 있음
    → 자바는 WORA(Write Once Run Anywhere)를 구현하기 위해 자바(정확히는 컴파일된 바이트코드)를 OS에 독립적으로 두고 대신에 OS별로 나눠진 JVM(자바 가상 머신)을 사용해서, 하나의 바이트코드를 수정없이 여러 OS의 JVM이 실행시켜줌
    → 바이트 코드는 jvm이 이해할 수 있는 중간 코드임

  • 사진을 보면 자바에만 JVM이 존재하는걸 볼 수 있는데, 그렇다면 자바 코드 실행 과정을 보면서 JVM이 무엇이고 C와 다르게 자바가 OS에 독립적일 수 이유가 JVM 때문인지도 알아보자

자바 컴파일 과정

컴파일 과정

  1. 개발자가 자바 소스코드(.java)를 작성함
  2. 자바 컴파일러가 자바 소스코드를 컴파일해서 자바 바이트코드(.class)로 번역해줌. 참고로 이 바이트코드는 아직 jvm이 읽을 수 없는 코드임
    • 바이트코드: 1바이트의 OpCode(Operation Code)와 추가 피연산자로 이뤄짐
  3. 바로 위에서 컴파일된 자바 바이트코드를 jvm의 클래스로더에 전달함
  4. 클래스로더는 동적 로딩을 통해 필요한 클래스들을 로딩 및 링크해 런타임 데이터 영역(jvm 메모리)에 올림
  5. 실행 엔진은 jvm 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와서 실행함
  • 더 자세한 내용은 밑에 계속.

JVM이란

  • jvm을 알기전 먼저 알아둬야 할 것
    jvm은 jvm 명세서만 보면 누구나 만들 수 있음. 대표적으로 오라클에서 만든 hotspot jvm도 있고 그 외의 회사에서 만든 다른 종류의 jvm도 있음. 한마디로 jvm을 알려면 jvm 명세서를 보면 되는데, jvm 명세서는 만드는 사람들의 재량에 따라 다양한 알고리즘과 기술들이 만들어질수 있도록 상세하게 기술하기보단 큰 그림으로 기술해놓았음. 뒤에 서술할 JIT 등도 이에 해당된다는 점에 미루어, JVM 명세서에는 없지만 JVM 아키텍처를 설명할때 존재하는 기술 및 알고리즘이 있다는 사실을 인지하자.
  • 런타임 환경에서 동작함

    런타임 vs 컴파일 타임
    - 컴파일 타임: 이때 발생하는 에러로는 문법 에러, 타입 체크 에러가 대표적인데, 런타임이 되기 전에 컴파일 오류가 발생해 런타임으로 가지 못함(실행되지 않음)
    - 런타임: 쉽게 말해서 프로그램이 실행될 때를 의미. 대표적으로 exception이 있는데 이건 컴파일에서 잡지 못하고 런타임중에 조건을 만족했을때만 발생함

  • jvm이 하는 일
    1. 자바 프로그램이 기기나 운영체제 상관없이 실행될 수 있게 하는 것=클래스 로더로 자바 애플리케이션을 읽어 런타임 데이터 영역에 할당하고 실행 엔진으로 실행하는 과정
    2. 런타임 중에 프로그램 메모리를 관리하고 최적화하는 것= garbage collection

jvm 요소

1. 클래스 로더(class loader)란

  • 클래스 로더는 런타임에 동적 로드를 해줌
    - 동적 로딩: 런타임 환경(프로그램이 실행되는 중)에서 어떤 클래스를 처음으로 참조할때 해당 클래스를 로드하고 링크(jvm의 메모리 영역에 할당)해줌 → 즉 jvm은 미리 모든 클래스에 대한 정보를 메소드 영역에 로딩하지 않음
    - 대표적인 동적 로딩 ex) 자바의 class 클래스, reflection
    - class 클래스: Class<?> myClass=Class.forName("com.example.MyClass");
  • 그럼 언제 클래스가 로딩될까?
    1. 클래스의 인스턴스를 생성할때
    2. 클래스의 상수(static final)를 호출할때
    3. 클래스의 정적 메소드를 호출할때

  • 클래스 로더의 특징
    1. 계층 구조: 위의 사진처럼 클래스 로더끼리 부모-자식 계층 구조로 되어있음. 최상위 클래스 로더는 부트스트랩 클래스 로더임
    2. 위임 모델: 클래스를 로드해야될때 먼저 상위 클래스 로더로 쭉 올라가(위임하여) 상위 클래스 로더에 있다면 해당 클래스를 사용하고, 없다면 다시 하위 클래스 로더로 쭉 내려오면서 맞는 클래스를 찾아 로드함
    3. 가시성(visibility) 제한: 위임 모델에 의해 하위 클래스 로더는 상위 클래스 로더의 클래스를 찾을 수 있으나 그 반대는 안됨
    4. 언로드(unload) 불가: 한번이라도 로드된 클래스는 jvm이 종료될때까지 메모리에 남아있어 중간에 임의로 메모리에서 해제할 수 없음. 다만 클래스 로더 자체를 삭제하는 방법을 쓸 수 있음
    5. 네임스페이스(namespace): 각 클래스 로더는 로드된 클래스를 보관하는 네임스페이스가 있어서, 어떤 클래스를 로드할때 이 네임스페이스로 이미 로드된 클래스인지를 판별할 수 있음
    6. 유일성 원칙: 하위 클래스로더가 상위 클래스 로더에서 로드한 클래스를 다시 로드하지 않아야 함(안그럼 동일한 클래스가 여러개 생기는 문제 발생함)
    → 클래스 로더가 클래스 로드를 요청받으면 클래스 로더 캐시→ 상위 클래스 로더로 쭉 위임→하위 클래스 로더로 내려오면서 해당 클래스를 찾는데 이미 로드된 클래스인지 판별하고 아니라면 파일 시스템에서 해당 클래스를 찾아서 로드함

  • 클래스 로더 기본 종류
    1. bootstrap class loader: jvm 기동시 생성되고 자바 자체의 클래스 로더와 Object,util,Class 클래스 등 기본 자바 api를 로드함
    2. extension class loader: 기본 자바 api를 제외한 확장 클래스들을 로드함
    3. system class loader: 애플리케이션의 클래스를 로드하는데 사용자가 지정한 $CLASSPATH 내의 클래스, jar에 속한 클래스를 로드함. 즉 개발자가 만든 .class 확장자 파일을 로드함

클래스 로더가 로드하는 과정

  • 클래스 로더가 아직 로드되지 않은 클래스들을 로드하는 과정

  • 로딩
    1. loading: 클래스 로더가 해당 클래스 파일을 찾아서 jvm의 메모리에 로드함

    • 클래스와 관련된 정보이므로 jvm의 메소드 영역에 동적으로 해당 클래스 정보들이 저장됨
    • JVM의 메소드 영역에 찾는 클래스가 로드되어있나?→ 있으면 그거 사용, 없으면 시스템 클래스 로더에 클래스 로드 요청→ 시스템 클래스 로더는 extension 클래스 로더에 요청을 위임→ extension 클래스 로더는 부트스트랩 클래스 로더에 위임→부트스트랩 클래스 로더에 해당 클래스가 있나?→없으면 밑에 로더들이 찾음-> 없으면 extension 클래스 로더가 찾음→ 시스템 클래스 로더에도 없으면 classNotFountException이 발생함
  • 링킹(Linking)
    2. verifying(검증): 클래스 로드 전 과정 중에서 가장 복잡하고 시간이 많이 걸리는 과정으로, 읽어들인 클래스가 자바 언어 명세 및 jvm에 명시된대로 구성되어 있는지 검사함
    3. preparing(준비): 클래스가 필요로 하는(필드/메소드/인터페이스) 메모리를 할당함.
    4. resolving(분석): 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경함

    • 자바 컴파일러는 클래스 파일을 생성할때, 상수 풀에 클래스의 정보(필드/메소드/인터페이스 등)를 대상의 이름만 지칭하고 실제 메모리 주소가 할당되지 않은 상태인 심볼릭 레퍼런스 형태로 저장함
    • 그러나 이 resolving 과정에서 이런 심볼릭 레퍼런스를 실제 메모리 주소로 변경하여 빠르게 클래스를 사용할 수 있게 준비해줌(다이렉트 레퍼런스)
  • 초기화
    5. initializing(초기화): 클래스 변수들을 적절한 값으로 초기화함(이때 static 필드가 설정된 값으로 초기화됨)

2. 런타임 데이터 영역(Runtime data area)이란

  • 런타임 데이터 영역: JVM이 OS 위에서 실행되면서 할당받는 메모리 영역을 의미함
  • 힙,메소드 영역은 모든 스레드가 공유하는 영역이고 그 외의 영역은 각 스레드당 하나씩 생성됨
  1. pc 레지스터

    • 현재 수행 중인 명령의 주소를 저장해놓는 곳으로, JVM 명령의 주소값이 저장되는 공간
    • 멀티 쓰레드 환경에서 한 쓰레드가 작업을 하다가 다른 쓰레드로 잠시 CPU 점유를 넘겨주고, 다시 돌아왔을 때 이전에 어떤 명령어를 수행하고 있었는지 기억하고 있어야(pc 레지스터) 이어서 작업을 수행할 수 있음
  2. 네이티브 메소드 스택(native method stack)

    • 자바 외의 언어의 호출을 위해 할당되는 공간.
    • 자바에서 C/C++의 메소드를 호출할 때 사용하는 Stack 영역이라고 생각하면 됨
  3. jvm 스택

    • 지역변수, 매개변수, 리턴값 등이 저장되며 LIFO 방식으로 메소드 실행시 저장되었다가 메소드가 끝나거나 예외가 터지면 제거되는 임시 저장공간임
    • 인스턴스 또는 객체같은 참조형 변수 정보가 저장하는 공간으로 GC 대상이 되는 곳
  4. 메소드 영역(method area)

    • JVM이 시작될때 생성되는 영역으로 jvm이 읽은 클래스와 인터페이스에 대한 필드/메소드/static 변수 등을 저장함
    • 런타임 상수 풀(runtime constant pool) =current class constant pool 영역
      - jvm의 동작에서 가장 핵심적인 역할을 수행하는 곳으로 클래스와 인터페이스의 상수(static final) 뿐만 아니라 메소드와 필드에 대한 모든 레퍼런스까지 담고 있는 테이블로, 어떤 메서드나 필드를 참조할때 jvm은 런타임 상수 풀을 통해 해당 메소드나 필드의 실제 메모리상 주소를 찾아서 참조함

3. 실행 엔진(execution engine)이란

  • 클래스 로더를 통해 런타임 데이터 영역에 배치된 바이트 코드를 명령어 단위로 읽어서 실행함. 쉽게 말하면 바이트 코드를 기계가 실행할 수 있는 형태로 변경해서 cpu에게 일을 시키는 것

  • 바이트 코드→기계어로 변형하는 방식
    1. 인터프리터: 기본적인 jvm 바이트 코드 방식으로, 바이트 코드 명령어를 하나씩 읽어서 해석하고 실행함. 해석은 빠르지만 전체적인 실행 속도가 느림.
    2. JIT 컴파일러(Just In Time): 인터프리터의 단점을 보완하기 위해 도입된 방법으로, 일반적으로는 인터프리터 방식으로 실행하다가 특정 메소드가 많이 호출되고 실행되는게 체크되면 바이트코드 전체를 컴파일하여 네이티브 코드로 변경하고, 해당 네이티브 코드는 캐시에 보관함. 캐시 덕분에 한 번 컴파일된 코드는 계속 빠르게 수행되게 됨. 전체적인 실행 속도는 인터프리터보다 빠름
    • 네이티브 코드: 특정 프로세스에서 직접 실행될 수 있는 기계어 코드(동일한 작업 내용이더라도 다른 프로세서 기반이면 컴파일 결과물이 달라짐)

4. 네이티브 인터페이스(native interface)란

해당 부분을 JVM에 포함하는 글, 포함하지 않는 글이 존재함.

  • 자바 언어로 작성된 코드가 네이티브 코드(C/C++)와 상호 작용할 수 있도록 함
  • ex. JNI(Java Native Interface): C/C++/어셈블리어로 작성된 함수를 자바 애플리케이션에서 사용할 수 있는 방법을 제공해줌. Native 키워드를 사용해 메소드를 호출하는데 대표적인 메서드 호출 방법은 ThreadcurrentThread()

5. 네이티브 메소드 라이브러리(native method libraries)

해당 부분을 JVM에 포함하는 글, 포함하지 않는 글이 존재함.

  • 네이티브 코드(저수준 언어 C/C++)로 작성된 메소드들의 집합임
    - 자바 언어가 아닌 메소드들을 자바 가상 머신이 가지고 있는 이유: 때로 운영체제나 하드웨어의 특정 기능에 접근해야될때 네이티브 인터페이스와 네이티브 메소드가 사용됨, 빠른 성능 요구하는 작업에 사용됨, 네트워크 관련 시스템 리소스를 직접 관리하고 제어할 수 있음 등등

0개의 댓글