본문 바로가기
  • 시 쓰는 개발자
1일 1개념정리 (24년 8월~)/Java

1일1개 (4) - JVM

by poetDeveloper 2024. 8. 12.

1일 1개념정리 24.08.09.금 ~ 

 

큰 결정에 큰 동기가 따르지 않을 때도 있다. 하지만 큰 결심이 따라야 이뤄낼 수 있다.

무조건 무조건 1일 1개의 개념 정리하기 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!


#4. JVM

Java를 공부하면 나오는 JVM이 무엇일까 ? 이는 Java Virtual Machine의 줄임말인데, OS에 종속받지 않고서 CPU가 자바를 실행하도록 도와주는 가상머신이다. 즉, 자바는 운영체제로부터 독립적으로 실행된다. 대체 이게 무슨말일까 ?? 애당초 OS에 종속받지 않고 CPU가 무언가를 실행하는 것이 가능하긴 한걸까 ??

JVM이란 ?

일단 위 질문에 대한 답은, VM을 이용하면 가능하다. 우리가 VM을 이야기할 때 보통 OS위에 VM을 올려놓는다고 표현한다. 즉, OS대신에 JAVA를 실행해주는 역할을 JVM이 해준다는 뜻이다. 그래서 구조가 JAVA - JVM - OS 이렇게 JVM이 가운데 위치해서 JAVA가 OS에 독립적으로 동작하도록 한다.

 

중요) 그래서 자바가 컴파일 되면 이 정보가 OS로 바로 들어가는 게 아니고, JVM에게 전달된다. 그럼 자바 컴파일 결과는 누굴 위한 결과일까 ?? 바로 JVM이다. 그래서 JAVA 컴파일의 결과는 "JVM이 이해할 수 있는 형태"여야한다. 그리고 그 결과물을 "ByteCode"라고 한다. 물론 이 바이트코드는 기계어가 아니라서 OS로 바로 넘길 수가 없다. 따라서 JVM은 바이트 코드를 다시 기계어(Binary Code = 01010100101)로 변환하는 작업을 거친다. 이러한 과정을 통해, Java가 OS로부터 독립적이게 만들어준다.

 

Java가 OS와 독립적이면 좋은 이유 (= JVM 장점)

읽으면서 의문이 든다. 그래서 JAVA가 OS랑 독립적이면 왜 좋은건데 ?? 어차피 기계어로 변환할건데 왜 JVM이 끼어서 굳이 바이트코드로 변환한 후, 기계어로 변환하는 고생을 하는 걸까 ?? 이는 이식성, 안정성, 보안성때문이다.

 

(1) 이식성

Write once, run anywhere. 자바의 표어라고 한다. Java는 일단 한 번 작성되면, JVM이 있는 어떤 OS에서도 실행될 수 있다. 그리고 이식성을 높이기 위해 OS에 종속적이지 않은 바이트코드로 변환하는 것이다. 왜냐하면 어차피 바이트코드는 JVM에 의해 실행되기 때문이다. 결과적으로 개발자는 OS별로 다른 코드가 아니라 하나의 코드를 짜면 여러 플랫폼에서 이를 실행시킬 수 있게 된다.

 

(2) 일관된 실행 환경

JVM은 Java 프로그램이 실행될 때 항상 동일한 실행 환경을 제공한다. 이는 프로그램이 OS나 하드웨어에 상관없이 동일하게 작동하도록 도와주고, 호환성 문제를 최소화한다.

 

(3) 보안성

JVM은 Java 프로그램이 실행되는 동안 샌드박스 환경(=격리된 환경, 즉 OS나 다른 프로그램에 영향을 받지 않고 코드를 실행하고 테스트 할 수 있는 공간)을 제공한다. 이를 통해 실행되는 프로그램은 JVM이 허용하는 범위 내에서만 자원을 접근할 수 있고, 이는 악의적인 접근 등을 막을 수 있다.

 

JVM의 구조

jvm의 구조

 

먼저 설명을 보지 않고, 위 그림을 보고 먼저 이해해보자.

 

일단 위 그림에서 보이듯 JVM은 크게 3가지 파트로 나뉘어져 있다. Class Loader, Execution, Runtime Data Area이다.

 

(1) Class Loader

말 그대로 클래스 파일을 로드한다. 컴파일하면 위 사진처럼 .class 파일이 생기고 이를 Runtime Data Area로 로드하는 역할을 한다. 여기서 Runtime Data Area은 JVM이 OS로부터 할당받은 메모리 영역을 뜻한다.

 

(2) Execution

지금까지의 상황은 컴파일 후 바이트코드가 만들어졌고 이를 Class Loader가 Runtime Data Area로 로드를 마친 상태이다. 그럼 이제는 Execution에서 이를 실행시킬 수 있다 !! 위 사진처럼 Execution에는 다음 3가지가 있는데, 정말 "실행"에만 초점을 둔다면 인터프리터와 JIT 컴파일러 방식이 있다고 말할 수 있다. 가비지 컬렉터는 실행 이후 청소하는 역할이므로 실행과는 약간 다른 맥락이라고 생각한다.

  1. Interpreter : 바이트코드를 명령어 단위로 읽으며 실행한다. 파이썬에서 이야기하는 인터프리터처럼, 여기서도 인터프리터는 한줄씩 수행되기 때문에 느리다.
  2. JIT Compiler (Just-in-time) : JIT 컴파일러는, 자주 사용되거나 반복되는 코드를 미리 컴파일하여 성능을 최적화하는 역할을 한다. 사실 모든 명령어를 위에처럼 인터프리터로 실행하면 파이썬과 다를바가 없다.... 느리고 매우 비효율적이다. 그래서 적당히 인터프리터로 실행하다가, "적당한 때"에 JIT 컴파일러가 바이트코드를 기계어로 변환하고 이를 실행시킨다. 이를 통해 인터프리터의 느린 실행 속도를 개선하고, 컴파일된 코드의 빠른 실행 이점을 동시에 누릴 수 있다.
    • 그렇다면 여기서 말하는 "적당한 때"는 무엇일까?? 한마디로, "빈번하게 실행되는 코드나 최적화가 필요한 부분이 감지된 시점"을 의미한다. 아래 예시를 보자.
      •    핫스팟(Hotspot) 탐지 : 프로그램 실행 중 자주 사용되는 코드를 핫스팟이라고 하고, 이런 부분이 발견되면 JIT 컴파일러는 해당 부분을 기계어로 컴파일하여 실행 속도를 높인다.
      •    실행 중 성능 모니터링 : JIT 컴파일러는 실시간으로 프로그램의 실행 패턴을 모니터링해서 어떤 코드가 시스템 자원을 많이 사용하는지 파악하고, 필요 시 해당 코드를 컴파일한다.
  3. Garbage Collector : 사용되지 않고 있는 메모리를 자동으로 회수한다. 힙에 로드된 객체를 주기적으로 탐색해서 참조되지 않고 있는 객체를 발견하여 제거하는 역할을 수행한다. 근데 GC가 청소할 땐 GC 스레드를 뺀 나머지 스레드는 동작을 멈춘다고 한다. 이렇게 GC스레드 빼고 모든 스레드가 일시정지 되는 것을 "Stop-the-World"라고 한다. 말 그대로 세상이 멈춘다 ... 생각해보면 프로그램이 잠시 멈춘다??? 이건 굉장히 위험해보인다. 근데 GC가 청소할 때 멈추는 것은 불가피하고, 현재는 이 시간을 최소화하거나 병렬 GC를 사용해 효율성을 높이는 방안들이 사용되고 있다고 한다.

(3) Runtime Data Area

위에 Class Loader에서 언급했듯이 OS로부터 할당받은 메모리영역을 의미한다.

 

Java 실행 순서

자 그럼 이러한 JVM 구조를 토대로 Java가 어떻게 실행되는지 알아보자.

  1. Java 프로그램을 실행한다.
  2. JVM은 OS로부터 메모리를 할당받는다. (= Runtime Data Area)
  3. 컴파일러가 소스코드를 바이트코드로 변환한다. ( .java → .class )
  4. Class Loader가 바이트코드(.class)를 Runtime Data Area에 로드한다.
  5. Execution이 이를 실행한다. 실행방법은 인터프리터 or JIT 컴파일러이다.
  6. 실행중에 GC가 메모리를 관리해준다.

 

JDK

JAVA를 공부하면 필수적으로 보는 단어이다. 예를들면 인텔리제이에서도 JDK 17 이런 문구를 본적이 있을 것이다. JDK는 JAVA Development Kit의 줄임말이다. 말 그대로 JAVA 개발시 필요한 도구들(키트)의 모음이다. 다음과 같은 것들을 포함한다.

  • Java Compiler : 위에서 언급했듯이 자바 코드를 바이트 코드로 변환
  • Java Runtime Environment (JRE) : 실행시 필요한 환경설정 내용 포함. 그래서 여러 자바 라이브러리가 포함됨.
  • Java Development Tools : 디버깅, 문서화 등 개발 "과정"을 지원하는 도구