[JAVA] JVM(Java Virtual Machine, 자바가상머신)

Sia Hwang·2022년 11월 8일
0

JAVA

목록 보기
1/6

What is the JAVA?

시작하기에 앞서 자바가 뭔가.
현시점에 사용되고 있는 핫한 프로그래밍 언어 중의 하나이고 또 순수한 객체지향언어라는 평가를 받는 프로그래밍 언어이다. 이러한 자바의 특징을 몇 가지 알아보자면

운영체제에 독립적이다.

  • 자바가 출시되기 이전에 존재했던 언어들과 달리 자바는 운영체제에 종속되지 않는다. 즉 자바 코드로 작성된 프로그램은 한 번만 제대로 만들어 놓으면 Windows, MacOS, Linux 등 운영체제에 상관없이 구동할 수 있는 것이다. 이게 왜 장점이냐면 .exe 프로그램을 맥에서는 실행할 수 없는 것을 생각해 보면 된다.
  • 이것을 가능하게 하는 것은 일종의 애뮬레이터인 JVM이 있기 때문이다. 자바는 오직 JVM과만 통신하며 JVM이 자바로 작성된 코드를 기계어로 번역해 운영체제에게 전달함으로서 프로그램이 실행되는 것이다.
  • 한 가지 구분해야 할 것은 자바로 작성된 프로그램은 운영체제에 독립적이지만 JVM은 운영체제에 종속적이기 때문에 자바를 출시했던 썬 마이크로시스템즈에서는 운영체제별로 다른 버전의 JVM을 제공하고 있다.
  • 그래서 자바를 표현하는 유명한 문구 '한번 작성하면, 어디서나 실행된다.(Write once, run anywhere)'가 성립되는 것이다.

객체지향언어이다.

  • 자바는 객제지향개념의 특징인 상속, 캡슐화, 다형성이 잘 적용된 순수한 객체지향언어라는 평가를 받고 있다.

자동 메모리 관리(Garbage Collection)

  • 자바로 작성된 프로그램이 실행되면 가비지컬렉터(Garbage Collector)가 자동적으로 메모리를 관리해 주기 때문에 메모리를 할당했다가 해제하고 하는 수고를 덜 수 있다. 특히 C/C++에선 프로그래머가 직접 메모리를 관리해줘야 하는데 할당은 잘 하지만 사용이 끝났는데 까먹고 해제를 해주지 않는 등 생각 외로 실수하는 경우가 많다.(이는 메모리 누수와 과부하의 원인이 된다) 하지만 자바에서는 이걸 관리해 주기 때문에 프로그래머는 보다 프로그래밍에 집중할 수 있게 된다.

네트워크와 분산처리를 지원한다.

  • 풍부하고 다양한 네트워크 프로그래밍 라이브러리(Java API)를 통해 비교적 짧은 시간에 네트워크 관련 프로그램을 쉽게 개발할 수 있도록 지원한다.

멀티스레드를 지원한다.

  • 일반적으로 멀티스레드(multi-thread)의 지원은 사용되는 운영체제에 따라 구현방법도 상이하며, 처리 방식도 다르다. 그러나 자바에서의 멀티스레드 프로그램은 시스템과 관계 없이 구현가능하며, 관련된 라이브러리가 제공되어서 구현이 쉽다. 그리고 여러 스레드에 대한 스케줄링을 자바 인터프리터가 담당하게 된다.

동적로딩(Dynamic Loading)을 지원한다.

  • 자바로 작성된 프로그램은 여러 개의 클래스로 구성되어 있다. 자바는 동적 로딩을 지원하기 때문에 실행 시에 모든 클래스가 로딩되지 않고 필요한 시점에 클래스를 로딩하여 사용할 수 있다. 그 외에도 일부 클래스가 변경되어도 전체 애플리케이션을 다시 컴파일하지 않아도 되며 애플리케이션의 변경사항이 발생해도 비교적 적은 작업만으로도 처리할 수 있는 유연한 애플리케이션을 작성할 수 있다.
  • 초창기 자바는 운영체제와 직접 통신하지 않고 JVM을 통해 기계어로 번역하는 과정을 거쳐야 하는 까닭에 속도가 느린 것이 최대 단점으로 지적되었으나 JIT컴파일러와 Hotspot과 같은 신기술 도입으로 JVM의 기능이 향상되어 속도문제가 상당히 개선되었다.

클래스 로더 시스템

  • 계층 구조 : 클래스 로더끼리 부모-자식 관계를 이뤄 계층 구조로 생성된다. 최상위 클래스 로더는 부트스트랩 클래스 로더(Bootstrap Class Loader)이다.
  • 위임 모델 : 계층 구조를 바탕으로 클래스 로더끼리 로드를 위임하는 형태로 동작한다. 클래스를 로드할 때 상위 클래스를 먼저 확인해서 로드되어야 하는 클래스가 있다면 그대로 사용하고 그렇지 않으면 로드를 요청받은 클래스 로더에서 로드한다.
  • 가시성(Visivility) 제한 : 하위 클래스 로더는 상위 클래스 로더를 찾을 수 있지만 반대 경우는 불가능하다.
  • 언로드(Unload) 불가 : 클래스 로더는 클래스를 로드할 수 있지만 언로드할 수는 없다. 대신에 현재 클래스 로더를 삭제하고 새로운 클래스 로더를 만드는 방법을 사용할 수 있다.

클래스 로더 위임 모델

  • Bootstrap Class Loader : JAVA_HOME\lib에 있는 코어 자바 API를 제공한다. 최상위 우선 순위를 가지고 있다.
  • Extension Class Loader : JAVA_HOME\lib\ext 폴더 또는 java.ext.dirs 시스템 변수에 해당하는 위치에 있는 클래스를 읽는다.
  • Application Class Loader : 애플리케이션 클래스 패스( 애플리케이션 실행할 때 주는 -classpath옵션 또는 java.class.path 환경 변수에 값에 해당하는 위치)에서 클래스를 읽는다.
  • User-Defined Class Loader : 애플리케이션 사용자가 직접 코드 상에서 생성해서 사용하는 클래스 로더

JVM(Java Virtual Machine)

  • 가상 컴퓨터(virtual machine)는 소프트웨어로 구현된 컴퓨터라고 생각하면 된다. 자바로 작성된 애플리케이션은 모두 JVM에서만 실행되기 때문에 자바 애플리케이션이 실행되기 위해서는 반드시 JVM이 설치되어 있어야 한다.

[이미지출처] https://www.edureka.co/blog/java-architecture/

  • 간략히 그림으로 요약해 보자면 이렇다. JVM에 대한 이해를 돕기 위한 그림은 정말 많지만 이해하기엔 심플한 것이 최고다.
  • 일반 애플리케이션 코드는 OS만 거치고 하드웨어로 전달되는데 자바 애플리케이션은 JVM을 통해 운영체제에 맞게 번역하는 과정을 거친 다음 하드웨어로 전달되기 때문에 비교적 속도가 느리다는 단점을 가지고 있다. 하지만 바이트코드(컴파일된 자바코드)를 하드웨어의 기계어로 바로 변환해주는 JIT컴파일러와 향상된 최적화 기술이 적용되면서 속도가 많이 빨라졌다.
  • 자바는 오직 JVM과만 통신하면 되기 때문에 여러 종류의 운영체제에 맞춰 프로그램을 새로 작성할 필요가 없다. 하지만 운영체제에 종속적인 언어같은 경우는 실행되어야 하는 운영체제가 바뀌면 그에 맞춰 프로그램을 재작성해야 하거나 수정이 필요하다.
  • 하지만 JVM 자체는 운영체제에 종속적이기 때문에 운영체제별로 맞는 JVM이 설치되어 있어야 한다.

코드 실행 순서

  • 실행을 위한 기본 클래스 파일(main 메서드를 포함하는 클래스)이 JVM으로 전달된 다음 최종 코드가 실행되기 전 3단계를 거친다.
  1. 로딩
    • 클래스 로더가 .class파일을 읽고 그 내용에 따라 적절한 바이너리 데이터를 만들어 메서드 영역에 저장한다. 메서드 영역에 저장하는 데이터는 다음과 같다.
		FQCN(Fully Quallified Class Name)
		클래스 | 인터페이스 | 이넘(Enum)
		메소드와 변수
  • 로딩이 끝나면 해당 클래스 타입의 Class 객체를 생성하여 힙(heap)영역에 저장한다.
  1. 링크
    Verify, Prepare, Resolve(Option) 세 단계로 나뉜다.
    검증(Verify) : .class 파일 형식이 유효한지 체크한다.
    준비(Preparation) : 클래스 변수(static 변수)와 기본값이 필요한 메모리 준비
    Resolve : 심볼릭 메모리 레퍼런스를 메모리 영역에 있는 실제 레퍼런스로 교체한다.

    심볼릭 레퍼런스 : 참고하는 클래스의 특정 메모리 주소를 참조 관계로 구성하지 않고 이름만 가지고 있는 것.

  2. 초기화

    • static 변수의 값을 할당한다(static block이 있다면 이 때 실행된다.)

JIT 컴파일러

  • 자바는 소스코드를 자바 컴파일러를 통해 바이트코드로 변환한 다음 JVM을 통해 변환되어 운영체제에게 전달되게 된다. 그래서 운영체제에 종속적인 언어에 비해서는 한 단계를 더 거치게 된다. 이 과정에서 사용하는 것이 인터프리터JIT 컴파일러이다. 그런데 이런 동작은 필연적으로 느려질 수 밖에 없기 때문에 속도 향상을 위해 JIT(Just-In-Time) 컴파일러가 도입되었다.

  • 바이트 코드를 인터프리팅(명령어를 하나씩 읽어서 해석하고 실행)하다 적절한 시점에 JIT 컴파일러로 바이트 코드 전체를 네이티브 코드로 바꾼다. 그리고 그 다음부터는 인터프리터가 네이티브 코드를 실행한다. 한 줄씩 인터프리팅하는 것 보다는 네이티브 코드를 실행하는 것이 훨씬 빠르기 때문에 이를 통해 실행 속도를 향상시킬 수 있다.

  • 하지만 JIT 컴파일러로 코드 전체를 컴파일하는 것은 시간이 오래 걸리는 일이다. 그렇기 때문에 한 번만 실행되고 마는 코드라면 인터프리팅하는 것이 속도면에서 훨씬 유리하다. 그래서 JIT 컴파일러를 사용하는 JVM은 각 메서드들의 수행 빈도를 파악한 후 빈도가 일정수를 넘을 때 컴파일을 시행한다.

JIT 컴파일러는 바이트코드를 우선 중간 단계의 표현인 IR(Intermediate Representation : 소스 코드를 표현하기 위해 컴파일러 또는 가상 시스템에서 내부적으로 사용하는 데이터 구조 또는 코드로 최적화와 번역 등 추가처리를 위해 도움이 되도록 설계됨)로 변환하여 최적화를 수행하고 그 다음 네이티브 코드를 생성한다.

JVM 구조

클래스 로더(Class Loader)가 컴파일된 자바 바이트 코드를 메모리영역에 로드하고, 실행 엔진(Execute Engine)이 컴파일된 바이트 코드를 실행한다.

Hotspot

  • Hot 한 Spot을 찾아 그 부분에서 JIT Compiler를 사용하는 JVM이라 할 수 있다.
  • 내부적으로 프로파일링을 통해 핫스팟을 찾아 해당 부분에 대한 네이티브를 생성하는데, 네이티브 코드를 만드는 방법으로 ClientServer 두 방법이 존재한다. 이는 Java SE안에 포함되어있어 자바 시작시 모드를 선택할 수 있다.

Client

  • 스타트업 시간과 메모리공간 최적화 중점 모드
  • Client 모드에서 동작하는 컴파일러는 주로 프로그램의 시작 시간을 최소하는데 집중한다. Client 모드에서는 바이트코드로부터 최대한 많은 정보를 뽑아 실제 동작하는 코드 블럭에 대한 최적화에 집중하며 전체적인 최적화는 관심없다.

Server

  • 다수의 request를 빠르게 처리하는데 중점을 둔 모드
  • Server모드에서 JIT은 부분적인 코드 실행보다는 전체적인 성능 최적화에 관점을 둔다. 크게 두 가지로 나눌 수 있다.
  1. 일반적인 컴파일러 최적화 기술들을 이용해 코드들을 최적화
  • 죽은 코드 삭제(Dead Code Elimination)
  • Loop 변수 끌어올리기(Loop invariants hoisting)
  • 공통 부분식 제거(Common Subexpression Elimination)
  • 상수 지연(Constant propagation)
  • 전역 코드 이동(Global Code motion) 등
  1. 자바에 최적화된 최적화 진행
  • Null Check 삭제
  • 배열의 Range Check 삭제
  • 예외처리 경로 최적화
  • 대단위 RICS 레지스터들을 최대한 활용하기 위한, Graph 연산을 통한 register 할당

메모리

  • 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역으로 스택 영역, PC Register, 네이티브 메소드 스택, 힙(heap), 메소드 영역으로 이루어진다.

스택 영역

  • 지역 변수, 파라미터, 리턴 값, 연산에 사용되는 임시 값등이 생성되는 영역으로 클래스 수준의 정보를 저장한다.

PC Register

  • 스레드가 생성될 때마다 생성되는 영역으로 현재 스레드가 실행되는 부분의 주소와 명령을 저장하고 있는 영역이다. 이것을 이용해 스레드를 돌아가며 수행할 수 있게 한다.

네이티브 메소드 스택

  • 자바 외 언어로 작성된 네이티브 코드를 위한 메모리영역으로 보통 C/C++등의 코드를 수행하기 위한 스택이다.
  • JNI(Java Native Interface)라고도 불리며 자바 언어 자체로 해결이 안되는 경우, 대처 할 수 있는 방법 중 하나이다.
    * Ex: Database 조회의 속도 성능 향상을 위해서 C/C++ 라이브러리 사용이 필요한 경우
  • 자바 애플리케이션에서 C, C++, 어셈블리로 작성된 함수를 사용할 수 있는 방법을 제시한다.

힙(heap)

  • new 키워드로 생성된 객체와 배열이 생성되는 영역이다. 메소드 영역에 로드된 클래스만 생성이 가능하고 Garbage Collector가 참조되지 않는 메모리를 확인/제거하는 영역이다.

메소드 영역

  • 모든 스레드가 공유하는 영역으로 JVM이 시작될 때 생성된다. JVM이 읽어들인 각각의 클래스, 인터페이스에 대한 런타임 상수 풀, 필드와 메서드 정보, static 변수, 메서드의 바이트코드 등을 저장한다.

JDK와 JRE의 차이

JDK(Java Development Kit): JRE + 개발 툴

  • 둘 중에서 JDK가 모두를 포함하는 넓은 개념이라고 보면 된다.
  • JAVA 11 이후로는 JDK만 배포된다.

JRE(Java Runtime Enviroment): JVM + 라이브러리

  • 자바 애플리케이션을 실행할 수 있도록 구성된 배포판.
  • JVM과 핵심 라이브러리 및 자바 런타임 환경에서 사용하는 프로퍼티 세팅이나 리소스 파일을 가지고 있다.
  • 개발 관련 도구는 포함하지 않는다.(JDK에서 제공)

    JDK 11 이후(Java Module 추가)로는 Jlink가 추가되어 JRE가 따로 배포되지 않는다.

참고

profile
당면한 문제는 끝까지 해결하기 위해 노력하는 주니어 개발자입니다.

0개의 댓글