기기

JVM에 대하여 본문

개발환경

JVM에 대하여

notEmpty 2020. 8. 27. 09:27

JVM

JVM은 자바 소스코드로부터 만들어지는 자바 클래스 파일(.class)을 실행할 수 있다. JVM은 플랫폼에 의존적이다. 예를들어 리눅스의 JVM과 윈도우즈의 JVM은 다르다. 하지만 컴파일된 바이트 코드는 어떤 JVM에서도 동작시킬 수 있기 때문에 java는 플랫폼에 독립적이다.

 

자바 코드 수행 과정

 

 

Class Loader

자바는 동적 로드, 즉 컴파일타임이 아니라 런타임에 클래스를 처음으로 참조할 때 해당 클래스를 로드하고 링크하는 특징이 있다. (필요할때마다 메모리에 찾고 없으면 올리기때문에 메모리에 중복으로 올라가는 클래스를 줄일 수 있다.) 

이렇게 생성된 클래스파일들을 엮어서 JVM이 운영체제로부터 할당받은 메모리영역인 Runtime Data Area로 적재하는 역할을 Class Loader가 한다.

 

클래스 로딩을 위한 JVM의 로딩 절차

  1. 어떤 메소드 호출 시 바이트 코드가 아직 로딩된 적이 없다면, JVM은 JRE 라이브러리 폴더에서 클래스를 찾는다.
  2. 없으면, CLASSPATH 환경 변수에 지정된 폴더에서 클래스를 찾는다.
  3. 찾았으면, 그 클래스 파일이 올바른지 바이트 코드 검증
  4. 올바른 바이트 코드라면 메소드 영역으로 파일을 로딩
  5. 클래스 변수를 만들라는 명령어가 있으면 메소드 영역에 그 변수를 준비한다.
  6. 클래스 블록이 있으면 순서대로 그 블록을 실행한다.
  7. 이렇게 한 번 클래스의 바이트 코드가 로딩되면 JVM이 종료될때까지 유지된다.

 

 

Runtime Data Area

런타임 데이터 구조

runtime data area는 JVM이 운영체제에 의해 할당받는 메모리 영역이다. 런타임 데이터 영역은 6개의 영역으로 나눌 수 있다. PC 레지스터(PC Register), JVM 스택(JVM Stack), 네이티브 메서드 스택(Native Method Stack)은 스레드마다 하나씩 생성되며,

힙(Heap), 메서드 영역(Method Area), 런타임 상수 풀(Runtime Constant Pool)은 모든 스레드가 공유해서 사용한다.

 

PC register

  • 각 스레드마다 하나씩 존재하며 스레드가 시작될 때 생성된다.
  • 스레드가 어떤 명령어로 실행되어야 할지에 대한 기록을 하는 부분으로 현재 수행중인 JVM 명령의 주소를 갖는다.

 

Stack Area

  • 각 스레드마다 하나씩 존재하며, 스레드가 시작될 때 할당된다.
  • 메소드를 호출할 때마다 프레임(Frame)을 추가(push)하고 메소드가 종료되면 해당 프레임을 제거(pop)하는 동작을 수행한다.
  • 메소드 호출 시 생성되는 스레드 수행정보를 기록하는 Frame을 저장
  • 메소드 정보, 지역변수, 매개변수, 연산 중 발생하는 임시 데이터 저장
  • 기본(Primitive) 타입 변수는 스택 영역에 직접 값을 가진다.
  • 참조 타입 변수는 heap area나 method area에 주소를 가진다.
  • heap area에 있는 객체가 stack area에서 참조할 수 없는 경우 GC의 대상이 된다.

 

 

Native Method Stack 

  • 자바 외의 언어로 작성된 네이티브 코드를 위한 스택이다.
    즉, JNI(Java Native Interface)를 통해 호출하는
    C/C++ 등의 코드를 수행하기 위한 스택으로, 언어에 맞게 C 스택이나 C++ 스택이 생성된다.

* Java Native Interface(JNI) 란?

Native Method Libraries와 상호작용하고 실행에 필요한 네이티브 라이브러리(C, C++)을 제공하는 인터페이스이다. JVM은 C/C++ 라이브러리를 통해 호출할 수 있고, 특정 하드웨어와 관련된 C/C++ 라이브러리로 호출 될 수도 있다.

 

* Native Method Libraries

Execution Engine에 인해 요구되는 네이티브 라이브러리(C,C++)의 모음이다.

 

 

<-- 여기까지가 각 쓰레드마다 갖는 영역 

 

 

Method(Static) Area

  • Method(Static) Area는 모든 스레드가 공유하는 영역으로 JVM이 시작될 때 생성된다.
  • JVM이 읽어 들인 각각의 클래스와 인터페이스에 대한 런타임 상수 풀, 필드와 메서드 정보, static 변수, 생성자, 메서드의 바이트코드 등을 보관한다.
  • 메서드 영역은 JVM 벤더마다 다양한 형태로 구현할 수 있으며,
    오라클 핫스팟 JVM(HotSpot JVM)에서는 흔히 Permanent Area, 혹은 Permanent Generation(PermGen)이라고 불린다.
  • 메서드 영역에 대한 GC는 JVM 벤더의 선택 사항이다.

 

Runtime Constant Pool

  • 클래스 파일 포맷에서 constant_pool 테이블에 해당하는 영역이다.
  • method area에 포함되는 영역이긴 하지만, JVM 동작에서 가장 핵심적인 역할을 수행하는 곳이기 때문에 JVM 명세에서도 따로 중요하게 기술한다.
  • 각 클래스와 인터페이스의 상수뿐만 아니라, 메서드와 필드에 대한 모든 레퍼런스까지 담고 있는 테이블이다.
  • 어떤 메서드나 필드를 참조할 때 JVM은 런타임 상수 풀을 통해 해당 메서드나 필드의 실제 메모리상 주소를 찾아서 참조한다.

 

Heap

  • 인스턴스 저장하는 공간으로 가비지 컬렉션 대상이다. JVM 성능 등의 이슈에서 가장 많이 언급되는 공간이다. 힙 구성 방식이나 가비지 컬렉션 방법 등은 JVM 벤더의 재량이다.

 

Garbage Collection

  • 힙 메모리 영역에 존재하는 객체들 중에 생존 여부를 판단하여 더 이상 사용되지 않는 객체를 제거하는 방식으로 메모리를 자동 관리한다.
  • new 연산자로 생성된 객체 또는 인스턴스와 배열을 저장한다.
  • 힙 영역에서 생성된 객체와 배열은 스택 영역의 변수나 다른 객체의 필드에서 참조한다.
  • 참조하는 변수나 필드가 없다면 의미 없는 객체가 되어 GC의 대상이 된다.

 

Execution Engine

Class Loader를 통해 '런타임 데이터 영역'에 적재된 바이트코드를 실행한다. 실행 엔진은 자바 바이트코드를 명령어 단위로 읽어서 실행한다. CPU가 기계 명령어을 하나씩 실행하는 것과 비슷하다. 바이트코드의 각 명령어는 1바이트짜리 OpCode와 추가 피연산자로 이루어져 있으며, 실행 엔진은 하나의 OpCode를 가져와서 피연산자와 함께 작업을 수행한 다음, 다음 OpCode를 수행하는 식으로 동작한다.

 

바이트코드는 실제로 JVM 내부에서 기계가 실행할 수 있는 형태로 변경하며, 그 방식은 다음 두 가지가 있다.

  • Interpreter: 바이트코드 명령어를 하나씩 읽어서 해석하고 실행한다. 하나씩 해석하고 실행하기 때문에 바이트코드 하나하나의 해석은 빠른 대신, 인터프리팅 결과의 실행은 느리다는 단점을 가지고 있다. 흔히 얘기하는 인터프리터 언어의 단점을 그대로 가지는 것이다. 

  • JIT(Just-In-Time) 컴파일러: 인터프리터의 단점을 보완하기 위해 도입된 것이 JIT 컴파일러이다. 인터프리터 방식으로 실행하다가 적절한 시점에 바이트코드 전체를 컴파일하여 네이티브 코드로 변경하고, 이후에는 해당 메서드를 더 이상 인터프리팅하지 않고 네이티브 코드로 직접 실행하는 방식이다. 네이티브 코드를 실행하는 것이 하나씩 인터프리팅하는 것보다 빠르고, 네이티브 코드는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 계속 빠르게 수행되게 된다.

자바 컴파일러와 JIT 컴파일러

JIT 컴파일러가 컴파일하는 과정은 바이트코드를 하나씩 인터프리팅하는 것보다 훨씬 오래 걸리므로, 만약 한 번만 실행되는 코드라면 컴파일하지 않고 인터프리팅하는 것이 훨씬 유리하다. 따라서, JIT 컴파일러를 사용하는 JVM들은 내부적으로 해당 메서드가 얼마나 자주 수행되는지 체크하고, 일정 정도를 넘을 때에만 컴파일을 수행한다.

 

 

 

 

 

 

 

 

 


바이트코드 vs 바이너리코드  vs 네이티브 코드

 

바이너리 코드란?

 

바이너리 코드는 컴퓨터(cpu)가 인식할 수 있는 0과 1로 구성된 이진코드를 의미한다.

 

 

 

기계어란?

 

1. 기계어는 바이너리 코드다.

 

2. 기계어가 이진코드로 이루어졌을 뿐이지, 모든 이진코드가 기계어인 것은 아니다. ( 바이너리 코드 != 기계어)

 

3. 기계어는 특정한 언어가 아니다.

 

   단지 CPU제조사에서 CPU를 만들 때 해당 CPU에서 사용하는 명령어 집합을 공개하는데, 이것을 '기계어'라고 부를 뿐이다.

 

   때문에 CPU가 변경되면 기계어가 달라진다. 같은 동작을 하는 명령어지만 완전히 다른 0과 1의 나열이 될 수 있다는 말이다.

 

4. 아주 기본적인 연산자들은 서로 호환이 되는 편이다.

 

5. 같은 회사의 CPU라도 버전 별로 다른 명령을 포함할 수 있으며, 다른 회사라도 같은 명령어 집합을 공유할 수도 있다.

 

 

 

바이트 코드란?

 

CPU가 이해할 수 있는 언어가 바이너리 코드라면, 바이트 코드는 가상 머신이 이해할 수 언어이다.

 

CPU가 아닌 가상 머신에서 이해할 수 있는 코드를 위한 이진 표현법이다. 즉, 가상 머신이 이해할 수 있는0과 1로 구성된 이진코드를 의미.

 

어떤 플렛폼에도 종속되지 않고 실행될 수 있는 가상 머신용 기계어 코드이다.

 

고급언어로 작성된 소스코드를 가상 머신이 이해할 수 있는 중간 코드로 컴파일한 것을 말한다.

 

바이트 코드는 다시 실시간 번역기 또는 저스트 인 타임(just-in-time, JIT) 컴파일러에 의해 바이너리 코드로 변환된다.

 

Java의 가상 머신을 JVM이라고 하며. JVM을 위한 바이트 코드를 자바 바이트 코드라고 한다.

 

 

 

 

 

 

c vs java

 

c   

.c소스파일         - > 컴파일                - >  목적파일 .obj  (바이너리 코드 ) - > 실행파일 .exe ( 100% 기계어 )

 

java

.java소스파일 - >  컴파일( javac ) - > 목적파일 .class ( 바이트코드 )    - > ( 인터프리터/ JIT 컴파일러 ) 고 JVM에 의해 실행 ( 바이너리 코드 )

 

 

 

 

 

바이트코드 vs 바이너리코드 https://shrtorznzl.tistory.com/82

 

 

참고

d2.naver.com/helloworld/1230

jeong-pro.tistory.com/148

 

 

 

 

 

'개발환경' 카테고리의 다른 글

eclipse 파일 저장시 공백 제거  (0) 2022.08.25
git - branch  (0) 2020.09.09