[Java 기초] JVM에 대해 알아보자
반응형

안녕하세요 😉

유유자적한 개발자 유로띠 입니다 😀

 

다시 초심으로 돌아가 공부를 하고자

기초부터 공부하기로 다짐하고 포스팅을 하게 되었습니다👍

 

 

 

 

 

 

 

이번 포스팅에서는

✅ JVM이란 무엇인가?

✅ JVM 흐름

✅ JVM 구조

✅ JVM TEST CODE

에 대해서 알아보겠습니다

 

책과 공부를 바탕으로 개인적으로 작성하였고

전반적인 내용을 쉽게 알기 위해 공부하였으므로

틀린 부분이 있으면 지적해주시면 감사하겠습니다.

 

 

🎉 Hello!! JVM World


📢JVM이란 무엇인가?

 

JVM은 Java Virtual Machine (자바 가상 머신)의 줄임말이며

JVM을 표현하는 가장 유명한 말이 있습니다.

 

Write Once, Run Anywhere (WORA) !!

 

✨ 한번 작성하면 어디에서나 실행한다.

 

자바가 공개되기 전에는 모든 컴퓨터 프로그램은

특정 운영체제에 맞게 작성되었기 때문에 새로운 운영체제가

생길 때마다 새로운 프로그램을 만들어야 했습니다.

 

JVM이 나타나기 전까지는 ❗❗

 

그래서 JVM의 특징은 다음과 같습니다.

 

🟢 플랫폼 어디에서든지 이식할 수 있습니다.

🟢 운영체제와 상관없이 독립적으로 관리할 수 있습니다.

🟢 자동으로 가비지컬렉터를 이용하여 메모리를 관리할 수 있습니다.

 

windows에서 컴파일을 한 파일을 서로 다른 운영체제인 mac이나 linux 이어도

JVM만 있으면 어느 환경이든 할 수 있는 구조입니다 👍

 

 

 


📢JVM의 흐름

 

JVM은 어떻게 동작하는지

JVM이 동작하는 흐름을 보도록 하겠습니다 😊

 

 

Java Source

  소스 코드인 .java파일은 사람이 이해할 수 있는 코드입니다.

Java Compiler (javac)

  JVM이 사람이 작성한 언어를 이해할 수 있도록 변환해 주는 것이 compiler입니다.

Java Byte Code

  자바 컴파일러(javac)가 byte code인 .class 파일로 만들어줍니다.

Class Loader

  클래스(.class 파일)들을 JVM의 메모리 영역인 Runtime Data Area에 로딩시킵니다.

Execution Engine

  로딩된 클래스들은 Execution Engine을 통해 해석되고 메모리에 적재돼 명령을 수행합니다.

 

 

 

자 그럼 본격적으로

JVM의 내부 구조에 대해서 알아보도록 하겠습니다 😁

 


📢JVM의 구조

 

JVM의 상세 내부 구조입니다

 

JVM Architecture Diagram

 

꽤 복잡해 보이지만 크게 5가지로 나뉩니다

 

Class Loader

Runtime Data Area

Execution Engine

Native Method Interface

Native Method Library

 

각 요소에 대한 자세한 설명은 차츰 공부를 하도록 하고

이번 포스팅에서는 전반적인 JVM의 구조를 쉽게 이해하기 위해서

가볍게 설명하도록 하겠습니다 😂

 

 

✅ Class Loader

Class Loader는 JVM 세상의 입구를 지키는 문지기와 같은 역할을 합니다.

왜냐면 Class Loader는 클래스들을 JVM의 메모리 영역인 Runtime Data Area에 로딩시키는 역할을 하고 있습니다. 

 

Runtime Data Area

운영체제에서 할당받은 메모리 영역입니다

여기서는 다시 5가지로 나뉩니다.

 

📍 Method Area

Method Area에는 바이트코드가 해당 영역에 저장됩니다.

우리가 생성한 클래스 정보, 변수 정보, static 변수, final 변수 등 정보가 저장되고 모든 스레드가 공유하게 됩니다.

 

📍 Heap

Heap 영역은 동적으로 생성한 객체가 저장되는 영역입니다.

동적으로 생성한 객체는 new 연산자를 이용한 객체입니다.

해당 영역은 가비지컬렉터(GC)의 대상이며 Method Area와 같이 모든 스레드가 공유하게 됩니다.

효율적인 GC를 위해 Heap은 다시 5가지 영역으로 나뉘지만 GC의 자세한 내용은 공부하여 따로 포스팅을 하도록 하겠습니다.

 

📍 Stack

Stack은 스레드 별로 독자적으로 가지고 있습니다.

지역변수나 메서드의 매개변수, 메서드 정보가 저장되는 영역입니다.

메서드 정보가 저장된다는 건 무슨 뜻일까요?

클래스로더(Class Loader)에 의해 로딩된 클래스는 아래처럼 객체화할 수 있습니다.

Car a = new Car();

new 키워드는 Heap에 충분한 여유 공간이 있는지 확인하고 Heap에 Car유형의 객체를 만들고 Stack에 있는 a(객체)의 참조하는 역할을 합니다.

Car(클래스)를 참조하는 a(객체)는 stack에 쌓이지만 동적으로 생성한 Car는 Heap영역에 저장됩니다.

 

문자열 비교를 통해 어떻게 참조하는지 보여드리겠습니다 😉

String one = "1";
String hana = "1";

if(one == hana)
  System.out.println("TRUE");
else 
  System.out.println("FALSE");

두 문자열 one, hana를 비교하면 결과는 TRUE로 나옵니다. 이유는 String유형의 두 참조를 비교한 후 실제 heap의 동일한 객체를 바라보기 때문입니다.

 

만약 아래와 같이 변경하면 어떻까요? 🧐

String one = new Integer(1).toString();
String hana = "1";

if(one == hana)
  System.out.println("TRUE");
else 
  System.out.println("FALSE");

이러한 경우 실제로 heap에 두개의 다른 객체가 있어서 결과는 FALSE가 출력됩니다.

 

 

여기서 잠깐 ❗❗

왜 JVM이 레지스트리를 안 쓰고 복잡한 스택(STACK)으로 구현한 이유가 무엇일까요 ❓

그 이유는 디바이스 별 레지스터의 수가 다르기 때문에 JVM의 큰 특징인 독립적인 플랫폼을 보장하기 위해 스택으로 구현하였다고 합니다.

이 꿀팁 정보는 해당 youtube에서 참고하였습니다.

[10분 테코톡] 무민의 JVM Stack & Heap

 

 

📍 PC Register(Program Counter Register)

스레드가 시작될 때 생성되며, 현재 수행 중인 JVM의 명령어 주소와 명령을 저장하는 공간입니다.

Stack과 마찬가지로 스레드 별로 독자적으로 가지고 있습니다.

 

📍 Native Method Stack

Java가 아닌 다른 언어로 작성된 코드를 위한 공간입니다

native 키워드가 붙은 것들이 저장되고 대표적으로는currentThread() 가 있습니다.

public static native Thread currentThread();

 

이상 Runtime Data Area의 5가지에 대해 알아보았습니다.

 

Execution Engine

Runtime Data Area에 로딩된 클래스들을 Execution Engine을 통해 해석됩니다.

바이트코드를 이해하기 쉽도록 기계어로 변환합니다.

변환하는 방식에는 인터프리터 방식과 JIT 방식이 있습니다.

 

🔷 인터프리터 방식

바이트코드를 하나씩 읽어서 실행하며, 결과는 느리다는 단점을 가지고 있습니다.

 

🔷 JIT(Just-In-Time) 방식 

인터프리터의 단점을 보완하기 위해 인터프리터 방식으로 실행하다가 적절한 시점에 바이트 코드 전체를 컴파일하여 네이티브 코드로 변경하고 이후 더 이상 인터프리팅을 하지 않고 변경한 네이티브 코드를 직접 실행하여 빠르게 처리하는 방식입니다.

 

📍 가비지컬렉터(GC : Garbage Collector)

클래스로더에 의해 로딩된 클래스들은 JVM안에서 객체화할 수 있습니다.

그런데 이러한 객체를 무한이 생성하면 JVM의 밀도가 높아지고 결국엔 OOME(Out Of Memory Error)가 발생됩니다 🤣

 

그래서 JVM의 특징 중 하나인 가비지컬렉터는 객체가 더 이상 참조가 없다고 판단되면 해당 객체를 JVM에서 소멸시켜 자동으로 메모리를 관리해 주는 역할을 합니다

 

 

Native Method Interface / Native Method Library

Java언어가 아닌 C, C++로 만들어진 고유기능을 사용할 수 있도록 하는 프레임 워크입니다.

또한 네이티브 메소드 실행에 필요한 라이브러리를 가지고 있습니다.

 

 


📢JVM TEST CODE

 

위에서 설명한 것처럼

실제로도 JVM이 어떻게 처리되는지

간단히 테스트를 해보도록 하겠습니다 ✌

 

 

✅ info.java

 

아래처럼 간단한 연산을 하는 소스코드를 작성해 보았습니다.

public class info {
	
	public static void main(String[] args) {
		
		double result = 1.0;
		double one = 1.0;
		double two = 1.0;
	
		result = (one+two) * 60.0;
		
	}
}

 

 

 

javap

 

JDK에 내장되어 있는 javap(역 어셈블러)를 사용하면 실제 동작되는 bytecode(바이트 코드)를 확인할 수 있습니다.

javap -c info.class

 

 

bytecode

public static void main(java.lang.String[]);
    Code:
       0: dconst_1
       1: dstore_1
       2: dconst_1
       3: dstore_3
       4: dconst_1
       5: dstore 5
       7: dload_3
       8: dload 5
      10: dadd
      11: ldc2_w        #16                 // double 60.0d
      14: dmul
      15: dstore_1
      16: return
}

 

◾ dconst_1 : double형인 1.0을 스택에 push한다.

 dstore_1 : 스택에서 pop하여 local variable 인덱스 1에 저장한다.

 dconst_1 : double형인 1.0을 스택에 push한다.

 dstore_3 : 스택에서 pop하여 인덱스 3에 저장한다.

 dconst_1 : double형인 1.0을 스택에 push한다.

 dstore 5 : 스택에서 pop하여 인덱스 5에 저장한다.

 dload_3 : local variable array 인덱스 3를 빼서 스택에 push한다.

 dload 5 : local variable array 인덱스 5를 빼서 스택에 push한다.

 dadd : 스택 맨 위 두개를 pop하여 add 연산하고 그 결과를 다시 스택에 push한다.

 ldc2_w #16 : 상수 풀인 60.0을 인덱스 #16에서 빼서 스택에 push한다.

 dmul : 스택 맨 위 두개의 double형을 곱하고 그 결과를 다시 스택에 push한다.

 dstore_1 : 스택에서 pop하여 인덱스 1에 저장한다.

 return : 메서드를 완료한다.



🔵 JVM은 자체적으로 메모리를 관리하므로 실제 메모리 주소 대신 #16과 같은 인덱스 번호를 사용합니다.

🔵 stack에 operand를 다 저장하기 때문에 따로 피연산자를 지정하지 않아도 연산이 가능합니다.

 

JAVA의 바이트코드 명령어는 [java bytecode instruction listings] 를 참고하였습니다.

 

 

이상

JVM의구조에 대해 알아보았습니다

👏👏👏

 

 

💡 참고 사이트

naver:JVM Internal

DZone:The JVM Architecture Explained

반응형