소스코드 파일 * .java     ->  바이트코드 파일  *.class

 

*.class는 JVM에서 읽음

JVM에서의 실행과정

  1. Class Loader를 통해 .class 파일들을 JVM에 올린다
  2. JVM에 있는 .class 파일들을 Execution Engine의 Interpreter와 JIT Complier를 통해 해석된다
  3. 해석된 바이트 코드는 Runtime Data Areas 에 배치되어 실질적인 수행이 이루어진다

 

       

Class Loader(Loading,linking,initializing)

자바프로그램을 실행하면 .java파일은 컴파일러를 통해 .class(자바 바이트코드)파일로 변환된다. Class Loader는 해당 파일을 Runtime(실행)하는 시점에 Runtime Data Areas에 로딩시킨다. 즉, 컴파일하는 시점이 아니라 클래스를 처음으로 참조하는 시점(클래스의 인스턴스를 만들 때) Class Loader를 통해 메모리(Runtime Data Areas)에 로드한다.

Runtime Data Areas

런타임 데이터 영역은 JVM이 OS 위에서 실행될 때 할당받는 메모리 영역이다.

- PC Register

현재 수행중인 JVM 명령의 주소가 저장되어있다. CPU의 Register와는 다름, 연산을 위해 필요한 피연산자를 임시로 저장하기 위한 용도로 사용한다.

- JVM Stack(메서드들 영역)..T메모리

Stack Frame을 저장하는 스택. JVM은 오직 Stack Frame을 push하고 pop하는 작업만 함. 예외 발생 시 printStackTrace() 등의 메서드로 보여주는 Stack Trace의 각 라인은 하나의 스택 프레임을 표현

  • Stack Frame(스택프레임) : 메서드가 수행될 때마다 하나의 스택 프레임이 생성되어 해당 스레드의 JVM 스택에 추가되고 메서드가 종료되면 스택 프레임이 제거된다. 각 스택 프레임은 Local Variable Array, Operand Stack, 현재 실행 중인 메서드가 속한 클래스의 Runtime Constant Pool 에 대한 레퍼런스를 갖는다. 지역 변수 배열, 피연산자 스택의 크기는 컴파일 시에 결정되기 때문에 스택 프레임의 크기도 메서드에 따라 크기가 고정된다.
  • Local Variable Array(지역변수 배열) : 0부터 시작하는 인덱스를 가진 배열. 0은 메서드가 속한 클래스 인스턴스의 this 레퍼런스이고, 1부터는 메서드에 전달된 파라미터들이 저장되며, 메서드 파라미터 이후에는 메서드의 지역 변수들이 저장된다.
  • Operand Stack(피연산자 스택) : 메서드의 실제 작업 공간. 각 메서드는 피연산자 스택 지역 변수 배열 사이에서 데이터를 교환하고, 다른 메서드 호출 결과를 push하거나 pop한다. 피연산자 스택 공간이 얼마나 필요한지는 컴파일할 때 결정할 수 있으므로, 피연산자 스택의 크기도 컴파일 시에 결정된다.

- Native Method Stack

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

- Heap(객체들이 있는곳)..T메모리

인스턴스(객체)를 저장하는 공간. GC(Garbage Collection)의 대상이다. 성능 이슈를 일으키는 공간

- Method Area(스테틱영역 클래스들)

JVM이 읽어 들인 각각의 클래스와 인터페이스에 대한 Runtime Constant Pool, 필드와 메서드 정보, Static 변수, 메서드의 바이트코드 등을 보관한다. 이 영역에 있는 클래스 정보로 Heap 영역에 객체를 생성한다. 즉, 매우 중요한 영역!

메서드 영역은 JVM 벤더마다 다양한 형태로 구현할 수 있으며, 오라클 핫스팟 JVM(HotSpot JVM)에서는 흔히 Permanent Area, 혹은 Permanent Generation(PermGen)이라고 불린다. 메서드 영역에 대한 가비지 컬렉션은 JVM 벤더의 선택 사항이다.

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

Execution Engine

Execution Engine  Class Loader를 통해 JVM 내의 Runtime Data Areas에 배치된 바이트코드를 실행한다. Execution Engine은 자바 바이트코드를 명령어 단위로 읽어서 실행한다

그런데 자바 바이트코드는 기계가 바로 수행할 수 있는 언어보다는 비교적 인간이 보기 편한 형태로 기술된 것이다. 그래서 실행 엔진은 이와 같은 바이트코드를 실제로 JVM 내부에서 기계가 실행할 수 있는 형태로 변경하며, 그 방식은 다음 두 가지가 있다.

  • Interpreter : 바이트코드 명령어를 하나씩 읽어서 해석하고 실행한다. 하나씩 해석하고 실행하기 때문에 바이트코드 하나하나의 해석은 빠른 대신 인터프리팅 결과의 실행은 느리다는 단점을 가지고 있다. 흔히 얘기하는 인터프리터 언어의 단점을 그대로 가지는 것이다. 즉, 바이트코드라는 ‘언어’는 기본적으로 인터프리터 방식으로 동작한다.
  • JIT(Just-In-Time) Compiler : 인터프리터의 단점을 보완하기 위해 도입된 것이 JIT 컴파일러이다. 인터프리터 방식으로 실행하다가 적절한 시점에 바이트코드 전체를 컴파일하여 네이티브 코드로 변경하고, 이후에는 해당 메서드를 더 이상 인터프리팅하지 않고 네이티브 코드로 직접 실행하는 방식이다. 네이티브 코드를 실행하는 것이 하나씩 인터프리팅하는 것보다 빠르고, 네이티브 코드는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 계속 빠르게 수행되게 된다.

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

GC

Garbage Collection은 메모리 관리를 위한 방법 중의 하나로, 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역[Heap]을 해제하는 기능. (new 연산자를 이용한 객체)

Java GC는 객체가 garbage인지 판단하기 위해 reachability 개념을 사용하여 객체의 유효한 참조가 있다면 ‘reachable’ 로 없으면 ‘unreachable’ 로 구별하고, unreachable 객체를 Garbage로 간주하여 GC를 실행한다. 한 객체가 다른 객체를 참조하며, 다른 객체는 또다른 객체를 참조할 경우에는 유효한 최초의 참조가 무엇인기 파악해야 되는데, 이를 객체 참조의 root set이라고 한다.

Heap 영역에 있는 객체들에 대한 참조의 종류는 4가지이다.

  1. Heap 내의 다른 객체에 의한 참조
  2. JVM Stack, Java 메서드 실행 시에 사용하는 지역 변수와 파라미터들에 의한 참조
  3. Native Stack, JNI(Java Native Interface)에 의해 생성된 객체에 대한 참조
  4. Method Area 의 static 변수에 의한 참조

이들 중 Heap 내의 다른 객체에 의한 참조를 제외한 나머지 3개(JVM Stack, Native Stack, Method Area)가 root set으로, reachability를 판가름하는 기준이 된다. 즉 root set으로부터 시작한 객체들은 reachable이며, root set과 무관한 객체들이 unreachable 객체로 GC의 대상이 된다.

 

------------------------

Jit Compiler

자주쓰이는 코드는 JVM이 machine code로 컴파일해서 속도를 높임.

바이트코드를 native code로 변환하는 작업은 JVM내에 별도의 쓰레드에서 이루어진다.

컴파일 작업이 끝나면 jvm은 바이트코드대신 컴파일된 버전을 사용함

Garbage Collector

실행되면 해당스레드외 다른스레들은 잠시 멈춘다.

가비지 컬렉션 과정 - Generational Garbage Collection

GC에 대해서 알아보기 전에 알아야 할 용어가 있다. 바로 'stop-the-world'이다. stop-the-world란, GC을 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것이다. stop-the-world가 발생하면 GC를 실행하는 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 멈춘다. GC 작업을 완료한 이후에야 중단했던 작업을 다시 시작한다. 어떤 GC 알고리즘을 사용하더라도 stop-the-world는 발생한다. 대개의 경우 GC 튜닝이란 이 stop-the-world 시간을 줄이는 것이다.

Java는 프로그램 코드에서 메모리를 명시적으로 지정하여 해제하지 않는다. 가끔 명시적으로 해제하려고 해당 객체를 null로 지정하거나 System.gc() 메서드를 호출하는 개발자가 있다. null로 지정하는 것은 큰 문제가 안 되지만, System.gc() 메서드를 호출하는 것은 시스템의 성능에 매우 큰 영향을 끼치므로 System.gc() 메서드는 절대로 사용하면 안 된다(다행히도 NHN에서 System.gc() 메서드를 호출하는 개발자를 보진 못했다).

(https://d2.naver.com/helloworld/1329..)

'자바' 카테고리의 다른 글

업캐스팅, 다운캐스팅  (0) 2022.07.02
불변객체,final  (0) 2022.06.28
람다와 함수형 인터페이스  (0) 2021.10.23
[Java]List Interface  (0) 2021.09.18
java 환경변수 3가지 셋팅  (0) 2017.05.05

+ Recent posts