본문 바로가기

STUDY/JAVA

JAVA-동작 원리

728x90

JAVA는 JVM(Java Virtual Machine)에서 동작하기 떄문에 호환되는 운영체제만 있다면 어떤 OS에서도 사용 가능하다.

그림1) JAVA의 동작 구성 (dalpang.e님 블로그)

 

그림2) JAVA 코드 실행 과정( dalpang.e님 블로그)

자바 코드는 위의 그림과 같은 단계로 실행이 된다.

 

  1. 자바 코드로 파일을 작성한다.
  2. 자바 컴파일러(javac)를 통해 자바 바이트 코드(*.class 파일)로 컴파일한다.
  3. 컴파일된 바이트 코드(*.class)를 JVM의 클래스로더(Class Loader)에게 전달한다.
  4. 클래스로더는 동적로딩(Dynamic Loading)을 통해 필요한 클래스들을 로딩,링크하여 Runtime Data Area(JVM의 메모리)에 올린다.
  5. 실행엔진이 JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 실행한다.

 

JVM의 내부

  1. 클래스 로더(Class Loader)
  2. 실행 엔진(Execution Engine)
  3. 런타임 데이터 영역(Runtime Data Area)

크게 3가지로 구성되어있다.

 

  1. 클래스 로더(Class Loader)

클래스 로더는 JVM내부로 클래스파일(*.class)을 동적으로 로드하고, 링크를 통해 배치한다. 

로딩 순서는 3단계로 구성이 된다. 

(Loading → Linking → Initialization) 

 

    • Loading(로드) : 클래스 파일을 가져와서 JVM의 메모리에 로드한다. 
    • Linking(링크): 클래스 파일을 사용하기 위해 검증하는 과정 
      • Verifying: 읽어들인 클래스가 JVM 명세에 명시된 대로 구성되어 있는지 검사
      •  Preparing: 클래스가 필요로 하는 메모리를 할당
      •  Resolving: 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경 
    • Initialization(초기화): 클래스 변수들을 적절한 값으로 초기화
  1. 실행엔진(Execution Engine)
    • 실행 엔진은 클래스로더를 통해 런타임 데이터 영역에 배치된 바이트 코드를 명령어 단위로 읽어서 실행. 
    • 바이트 코드(*.class)는 기계가 수행하는 언어이기보다는 가상머신이 이해할 수 있는 레벨의 컴파일된 코드 
    • 그렇기 때문에 실행 엔진이 바이트코드를 JVM내부에서 기계가 실행할 수 있는 형태로 변경해준다. 
    • 변경 과정에서 실행 엔진은 인터프리터와 JIT 컴파일러 두가지를 혼합해 바이트 코드를 실행한다. 
      • 인터프리터 : 바이트 코드 명령어를 하나씩 읽고 해석 및  실행. 하나하나의 해석은 빠르지만 전체적인 실행 속도는 느린 단점이 있다. JVM 내부에서 바이트코드는 기본적으로 인터프리터 방식으로 동작 
      • JIT 컴파일러(Just-In-Time Compiler) : 인터프리터의 단점을 보완하기 위해 도입된 방식. 바이트 코드 전체를 컴파일하여 네이티브 코드로 변경하고 이후에는 해당 메서드를 더 이상 인터프리팅 하지 않고 네이티브 코드로 직접 실행하는 방식이다. 바이트 코드 전체가 컴파일된 네이티브 코드를 실행하기 때문에 전체적인 실행 속도는 인터프리팅 방식보다 빠르다
  2. 런타임 데이터 영역(Runtime Data Area) 
    • 메서드 영역(Method Area) : 모든 스레드가 공유하는 영역,  JVM이 시작될 때 생성. JVM이 읽어 들인 각각의 클래스와 인터페이스에 대한 런타임 상수 풀, 필드와 메서드에 대한 정보, Static 변수, 메서드의 바이트 코드 등을 보관
    • 런타임 상수 풀(Runtime Constant Pool) : JVM 동작에서 가장 핵심적인 역할을 수행하는 곳, JVM 명세에서도 따로 중요하게 기술. 각 클래스와 인터페이스의 상수 뿐만 아니라, 메서드와 필드에 대한 모든 레퍼런스까지 담고 있는 테이블로 어떤 메서드나 필드를 참조할 때 JVM은 런타임 상수 풀을 통해 해당 메서드나 필드의 실제 메모리상 주소를 찾아서 참조
    •  : 인스턴스 또는 객체를 저장하는 공간으로 가비지 컬렉션(Garbage Collection) 대상. JVM 성능 등의 이슈에서 가장 많이 언급되는 공간. 힙 구성 방식이나 가비지 컬렉션 방법 등은 JVM 벤더들의 재량
    • JVM 스택(JVM Stack) : 스택 프레임(Stack Frame)이라는 구조체를 저장하는 스택. 예외 발생 시 printStackTrace() 메서드로 보여주는 Stack Trace의 각 라인 하나가 스택 프레임을 표현. JVM 스택 역시 PC 레지스터와 마찬가지로 스레드가 시작될 때 생성되며 각 스레드마다 하나씩 존재
    • PC 레지스터(PC Register) : PC(Program Counter) 레지스터는 현재 수행 중인 명령의 주소를 가지며 스레드가 시작될 때 생성되며 각 스레드마다 하나씩 존재
    • 네이티브 메서드 스택(Native Method Stack) : JAVA 외의 언어로 작성된 네이티브 코드를 위한 스택. JNI(JAVA Native Interface)를 통해 호출하는 C/C++ 등의 코드를 수행하기 위한 스택, 언어에 맞게 스택이 생성. (C면 C스택, C++이면 C++스택 생성)

가비지 컬렉션

  • Garbage Collection, 줄여서 GC라고 부른다.
  • 메모리 관리 방법중 하나로 시스템에서 더이상 사용하지 않는 동적 할당된 메모리 블럭을 찾아 자동으로 다시 사용 가능한 자원으로 회수하는 것
  • 시스템에서 가비지컬렉션을 수행하는 부분을 가비지 컬렉터라 부른다.
  • 가비지 컬렉션이 없다면 프로그래머가 메모리를 할당한 뒤 수동으로 해제까지 직접 해야 한다.
  • 메모리를 할당해놓고 필요 없어진 뒤에도 해제를 안하여 메모리 누수가 생기거나 해제했던 메모리를 다시 사용하는 실수 등에서 버그가 양산
  • 이런 문제들을 해결하기 위해서 제시된 것이 가비지 컬렉션이다. 가비지 컬렉션에서 제공하는 할당과 해제를 이용하여 프로그램이 실행되며 생기는 쓸모없어지는 메모리들을 알아서 수집하고 관리해준다.

가비지 컬렉터

가비지 컬렉터의 역할은 아래와 같다.

  • 메모리 할당
  • 사용중인 메모리 인식
  • 사용하지 않는 메모리 인식

즉, 메모리가 부족할 때 쓰레기를 정리해주는 프로그램을 가비지 컬렉터라고 한다.

프로그램을 실행할때 메모리를 관리하는 OS에 프로그램 실행에 필요한 메모리를 요청하게 된다.

이때 주소를 할당하는데 이 주소를 offset 주소라고 부른다.

이 할당된 메모리들은 프로그램이 돌아가며 가비지가 발생하게 된다. 기존에 가리키고 있던 메모리를 새롭게 선언되거나 형변환이 되며 다른곳을 가리키게 된다. 그러면 주소를 잃어버리고 다시 찾을 수 없게 되면서 정리되지 않은 메모리가 생기기 때문이다.

그래서 가비지 컬렉터는 가비지를 다른 용도로 사용할 수 있도록 메모리 해제를 시킨다. 이것이 가비지 컬렉터의 목적이다.

 

따라서 자바에서는 JVM이 메모리를 부여받아 프로그램을 실행하고,  메모리가 부족해지는 순간이 오게되면 추가적으로 메모리를 요청한다. 이때 가비지 컬렉터가 실행된다.

728x90

'STUDY > JAVA' 카테고리의 다른 글

JAVA-객체 지향 프로그래밍이란  (0) 2023.12.28