[Java] Java 언어의 특징과 JVM 동작 원리

KIM Jongwan·2024년 6월 7일
0

JAVA

목록 보기
3/4
post-thumbnail

Java 언어의 특징과 JVM(Java Virtual Machine) 의 동작 원리를 이해할 수 있다.

Java는 리모콘, 냉장고, 전자레인지 등 가전 제품에 적용될 임베디드 소프트웨어를 작성하기 위해 개발된 언어입니다. Java가 등장하기 전까지도 C/C++와 같은 언어들을 사용하여도 충분히 작업 가능한 영역이었으나, 이들은 CPU나 OS환경이 변경 될 경우 그에 맞춰 다시 작업해주어야하는 어려움이 있었습니다.

당시 썬 마이크로시스템즈에서 근무하던 제임스 고슬링(James Gosling)은 이러한 문제점을 극복하기 위해 1991년 그린 프로젝트라는 이름으로 Java 언어를 개발하기 시작하였고, 1995년 Java 1.0을 발표하게됩니다.

이러한 Java 언어에는 몇가지 중요한 특징이 있습니다.

Java 언어의 특징

1. 운영체제의 독립적

Java는 운영체제에 독립적인 언어입니다. Java로 작성된 프로그램은 하드웨어나 OS에 상관 없이 JRE(JVM + Java 표준 라이브러리)만 설치되어 있다면 어디서든 실행이 가능합니다.

2. 객체지향 언어(OOP, Object-Oriented Programming)

Java는 객체지향 프로그래밍언어입니다. 이는 기존 절차지향 프로그래밍 언어와는 다르게 데이터를 추상화한 객체(Object)를 위주로 프로그램을 구현하게됩니다.

객체지향 언어는 코드를 재사용하여 반복되는 코드 작성을 줄이고, 보다 간결한 표현이 가능합니다. 때문에 잘 구현된 객체지향 프로그램은 코드를 보다 직관적으로 만들고 유지보수가 용이하게 합니다.

Java는 객체지향 언어의 특징인 추상화, 상속, 다형성, 캡슐화를 지원합니다.

3. 메모리 자동 관리 (GC, Garbage Collector)

이전에 사용되었던 C/C++과 같은 언어들은 프로그래머가 직접 메모리의 생성과 소멸을 관리해주어야했습니다. 이는 사용하지 않는 자원의 누수나 과도한 자원 할당등의 위험성이 있습니다.
Java는 Garbage Collector를 지원합니다. 이는 프로그래머가 메모리 자원을 관리할 필요 없이 스스로 사용하지 않는 메모리를 반납합니다.


JVM(Java Virtual-Machine) 동작 원리

Java의 핵심인 운영체제의 독립성은 JVM이 있기에 가능합니다. JVM이란 무엇이며 동작 원리는 무엇인지 알아보도록 합시다.

JVM 이란?

JVM, Java Virtual-Machine은 자바 코드를 실행하기 위한 가상 컴퓨터입니다.. Java는 인간 수준에서 이해할 수 있는 고급 언어기 때문에 자바 원시 코드(*.java)는 기계가 직접 이해할 수 없습니다.
Java compiler는 자바 원시 코드를 자바 바이트 코드(*.class)로 변환하는 역할을 하는데, JVM은 이 변환된 자바 바이트 코드를 인식해 실행할 수 있도록 기계가 이해할 수 있도록 번역하는 역할을 합니다.

*java 코드 실행 과정*

자바 코드 실행 순서 (출처: Oracle)


JVM 동작 원리

자바 코드 실행 상세


개발자에의해 작성된 자바 소스 파일(*.java)은 아래와 같은 단계적 절차를 거쳐 실행됩니다.

  1. 개발자에 의해 작성된 자바 소스 파일(*.java)자바 컴파일러(java compiler)를 통해 중간 언어인 자바 바이트 코드(*.class)로 변환된다.

  2. 변환 된 자바 바이트 코드는 JVM의 클래스 로더(Class Loader)에게 전달된다.

  3. 클래스 로더는 전달받은 자바 바이트 코드의 실행에 필요한 클래스들을 런타임 데이터 영역(JVM 메모리 내 존재)에 올린다.

  4. 실행 엔진은 런타임 데이터 영역에 로드 된 자바 바이트 코드를 명령어 단위로 읽어들이며 실행한다.

다음으로 JVM 내부 구조에 대해 조금 더 자세하게 살펴봅시다.

Class Loader

Java는 컴파일 타임이 아닌 런타임에 클래스를 최조로 참조할 때 해당 클래스를 로드하고 링크하는 동적 로드의 특징을 갖습니다.. 이 동적 로드를 담당하는 부분이 JVM의 클래스 로더입니다.

클래스 로더의 특징

  • 계층 구조
  • 위임 모델
  • 가시성
  • 언로드 불가
  • 네임스페이스

- 계층 구조

클래스 로더는 서로 다른 부모-자식 관계의 클래스 로더들로 이루어진 계층 구조로 생성됩니다.

클래스 로더 구조


위 그림과 같이 최상위 클래스 부트스트랩 클래스 로더를 시작으로 계층 구조로 이루어져있습니다.

각각의 클래스 로더들의 특징에 대해 알아봅시다.

  • 부트스트랩 클래스 로더(Bootstrap Class Loader)
    • 최상위 클래스 로더로 유일하게 Java 코드가 아닌 네이티브 코드로 작성되어있다.
    • JVM 실행 시 메모리를 할당 받는다.
    • Object 클래스를 비롯한 Java API들을 로드한다.

  • 익스텐션 클래스 로더(Extention Class Loader)
    • 기본 자바 API를 제외한 확장 클래스들을 로드한다.
    • 다양한 보안 확장 기능을 로드하게 된다.

  • 시스템 클래스 로더(System Class Loader)
    • 애플리케이션의 클래스들을 로드한다. (반면 부트스트랩, 익스텐션 클래스 로더는 JVM 구성요소 자체를 로드)
    • 사용자가 지정한 $CLASSPATH 내에 클래스들을 로드한다.

  • 사용자 정의 클래스 로더(User-Definiation Class Loader)
    • 애플리케이션 사용자가 직접 코드 상에서 생성하여 사용하는 클래스 로더이다.

- 위임 모델

클래스 로더가 클래스 로드 요청을 받게 되면 1)클래스 로더 캐시, 2)상위 클래스 로더, 3)자기 자신 순서대로 요청 받은 클래스의 존재 유무를 확인합니다.
클래스 로더 캐시를 시작하여 점점 상위로 올라가며 클래스를 찾는 과정은 부트 스트랩 클래스 로더를 확인 할 때까지 진행되며, 부트스트랩 로더에도 해당 클래스가 존재하지 않는다면 파일 시스템에서 찾는 과정을 계속 하게됩니다.

한 가지 주목해야할 사항은 클래스를 찾는 과정 도중에 요청받은 클래스가 발견되더라도 부트스트랩 클래스 로더는 확인 할 때 까지 과정은 계속해서 진행되며, 최상위 클래스 로더에서 발견된 클래스를 로드하게됩니다.

- 가시성

클래스 로더가 클래스 로드 요청을 받았을 때, 클래스를 확인하는 절차는 상위 클래스 로더로 올라가며 진행되지만, 이 때 하위 클래스 로더를 확인하는 것은 불가능하다는 특성이 가시성 제한입니다.

- 언로드 불가

클래스를 로드하는 작업은 가능하지만 반대 작업인 언로드는 불가능합니다.

- 네임스페이스

각각의 클래스 로더는 로드된 클래스를 보관하는 네임스페이스 공간을 갖습니다.
로드 할 클래스를 확인할 때 이미 로드 된 클래스인지 확인하기 위해 네임스페이스의 FQCN(Full Qualified Class Name)을 확인합니다.
FQCN이 같더라도 네임스페이스가 다르면 다른 클래스로 간주됩니다.

(추가) 클래스 로드 단계

클래스 로더가 발견된 클래스를 로드하는 단계는 아래 그림과 같이 진행됩니다.

클래스 로드 단계


  • 로드
    • 클래스 파일을 가져와 JVM 메모리에 로드합니다.
  • 검증
    • 로드 된 클래스 파일이 Java 명세나 JVM 명세에 위배되는 사항이 없는지 검사합니다. 클래스 로드 과정 중 가장 긴 시간을 필요로 합니다.
  • 준비
    • 클래스에 필요한 메모리를 할당합니다.
  • 분석
    • 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경합니다.
    • 심볼릭 레퍼런스란? 기본 자료형을 제외한 모든 참조형 자료형의 경우 데이터가 메모리에 저장된 직접적인 주소 기반 레퍼런스가 아닌 심볼릭 레퍼런스를 참조합니다.
  • 초기화
    • 클래스 변수들을 적절한 값으로 초기화합니다.

Runtime Data Area

JVM이 실행되면서 OS로부터 할당 받은 메모리를 관리하는 곳이 런타임 메모리 영역입니다.
런타임 메모리 영역은 Method Area, Heap, JVM Stacks, PC Registers, Native Method Stacks로 구성되어있습니다.

이중 Method Area와 Heap영역은 모든 Thread가 공유(동시성 이슈에 고려 필요)하는 자원이고
JVM Stacks, PC Registers, Native Method Stacks각각의 Thread가 생성될 때 독립적으로 할당받는 자원입니다.

런타임 데이터 영역


각 요소에 대하여 좀 더 자세하게 알아보도록 합시다.

  • Method Area

    • 클래스에 대한 모든 정보가 저장됩니다.(스레드 공유 영역)
  • Heap

    • 런타임시 생성되는 모든 객체가 생성됩니다. (GC가 주로 Heap영역에서 활동한다.) (스레드 공유 영역)
  • JVM Stacks

    • 메소드를 실행하기 위한 정보들이 저장됩니다. 메소드 실행 시 Frame이라는 자료구조 형태로 JVM Stack영역에 저장되며 생명주기를 다 할 경우 제거됩니다.
    • Frame 구조
      • Current Class Constant Pool Reference
      • Local Variables Array
      • Operand Stack
  • PC Registers

    • 현재 실행되고 있는 명령어의 주소를 저장합니다.
  • Native Method Stacks

    • Java코드가 아닌 C/C++로 작성된 메소드를 실행할 때 사용됩니다.

Execution Engine

실행 엔진은 런타임 데이터 영역에 배치된 바이트 코드를 명령어 단위로 읽어와 실행하는 역할을 합니다. Java의 실행 엔진은 바이트 코드를 기계가 실행 할 수 있는 언어로 번역하기 위해 인터프리터와 JIT 컴파일러 두 가지 방식으로 번역을 수행합니다.

참고

profile
3년차 백앤드 개발자입니다.

0개의 댓글