본문 바로가기

Backend/Java

[JAVA] JVM과 JAVA code의 실행 과정

Java...

 

흔히들 우리나라에서 가장 많이 사용되는 언어가 무엇인지에 대해 묻는다면, Java라고 대부분이 답할 것이다.

 

Java는 몇 년도에 누가 만들었고, 라는 형식적인 글보다는 왜 Java가 선택받았는지에 대해 나는 궁금했다.

 

필자는 C++/Python을 주로 사용하여 그동안 프로젝트를 대부분 진행하였다. 그 이유는 OOP(Object-Oriented Programming)을 메인으로 코드를 디자인하지만, 어느 정도 절차적인 언어의 형식도 있어야 뭔가 코딩하는 입장에서 더 편했다. 

 

(개인적으로 C++/Python 코드가 Java 코드보다 더 예쁘다...)

 

위 두 언어와 다르게 Java는 모든 코드가 Class로 이루어진 객체 지향 프로그래밍 언어이며 따라서 어느정도 초심자의 입장에서 진입장벽이 있을 수밖에 없다.

 

그렇다면 왜 JAVA는 현재 가장 많이 사용되는 언어중 하나로 살아남게 되었을까?

 

1. JVM 소개  

Java가 처음 등장하던 시기에 주 프로그래밍 언어로 자리 잡고 있던 C/C++는 큰 문제가 있었다.

 

바로 플랫폼(OS, CPU architecture)에 독립적이지 못했다는 점...

 

즉 내가 C/C++ base로 짠 프로그램을 Windows에서 컴파일을 했으면 해당 파일이 Linux/Mac에서는 호환이 안된다는 점이다.

 

이는 근데 당연할 수 밖에 없었던 게, OS마다 system call 호출 interface가 모두 다르고 CPU architecture마다 명령어(instruction) 형식이 모두 다르기 때문에, 컴파일 한 해당 환경에서 작동하는 코드가 다른 플랫폼을 만났을 때 오류가 있을 수밖에 없다.

 

이를 해결하기 위해 C 진영에서는 Cross Compile이라는 것을 내었고, 윈도우에서 Linux용 프로그램 파일을 컴파일할 수 있게 만들어 주었다.

 

하지만 이는 근본적인 해결책이 아니며, 모든 프로그램을 해당 OS에 맞추어, CPU architecture에 맞추어 컴파일해 실행파일을 만드는 것은 비효율성을 초래한다.

 

여기서 JVM라는 하나의 혁명이 발생한다.

 

Write Once, Run Anywhere - Sun Microsystems

 

Java로 작성된 프로그램을 한번 컴파일하면, JVM이 타겟 플랫폼에 맞추어 Java bytecode를 알아서 변환해준다...

 

즉 Windows, Linux, Mac 상관없이 JVM만 존재한다면, 컴파일된 코드를 실행할 수 있다는 것이다.

 

물론 해당 OS의 JVM을 설치해야 하겠지만, 기본적인 실행코드를 플랫폼 상관없이 실행할 수 있다는 점은 위 C/C++의 문제를 근본적으로 해결한 해결책이었다.

 

여기서 굳이 JVM을 사용해야 하나? 라는 의문이 들 수도 있다.

 

지금이야 많은 발전이 이어져서 여러 크로스 플랫폼 언어가 나오지만,

 

IT가 막 발전하던 시기에는 내가 핸드폰 앱을 하나 만들면, 해당 앱이 삼성 폰과 같은 Android OS 기반 머신에서도 작동해야 하고, 다른 OS 기반 머신에서도 작동해야 한다고 생각해보자. 매번 새로운 앱이나 프로그램이 개발될 때마다 여러 머신마다 타게팅 컴파일을 진행해야 하고, 해당 과정을 한 번으로 줄여준 Java는 선택받을 수밖에 없었던 것이다...

 

그러면 간단하게 Java 코드가 실행되는 과정을 살펴보자.

 

컴퓨터는 기계이다. 기계는 사람의 언어를 이해할 수 없다. 

 

즉 사람이 1 + 1 = 2 라는 간단한 산술식을 눈으로 풀 수 있지만 기계(컴퓨터)는 해당 산술식을

 

register1 = 0001 (2진수)

register2 = 0001 (2진수)

register3 = add register1 register 2... (0010)

(정확하지 않아요. 간단하게 적은 거입니다...)

 

이런식으로이런 식으로 register 1에 0001이라는 수를 저장하고, register 2에는 0001이라는 수를 저장하고 해당 add 연산을 거친 뒤 register 3에 저장... 이런 식으로 일차원적인 명령 흐름으로 바꾸어 주어야 한다.

 

저 과정에서 add register1과 같은 명령어가 CPU architecture마다 다르기 때문에 C/C++로 컴파일 한 코드(명령어 모음)이 다른 CPU에서는 실행이 되기 어렵다는 것이다.

 

Java는 기본적으로 Java Compiler(javac)라는 프로그램이 구문 분석을 통해 해당 코드를 Java Bytecode라는 위에서 본 일종의 명령어 모음(어셈블리 언어)으로 변경해 준다.

 

이 명령어 모음(어셈블리 언어)를 JVM이 해당 플랫폼에 맞추어 실행할 수 있게 바꾸어 주는 것이다.

 

2. JAVA 실행 과정

 

<Java 실행 과정 도식도>

필자와 같은 사람이 java 코드를 작성하고 run을 누른 뒤 과정이다.

 

실행이 되기 시작하면 우선 javac가 프로그래머가 작성한 .java file을 .class file로 변환해 준다. 

 

그러면 Class Loader라는 일종의 Subsystem이 해당 .class파일을 받아와서 분석한다.

 

이 과정에서 OS는 해당 프로그램을 실행하기 위한 메모리영역을 할당하여 준다.

쉽게 생각하면 내 컴퓨터에 메모리가 ~~만큼 있으면, 그중 해당 프로그램을 실행할 때 OS가 ~부터 ~까지 사용해...라는 식으로 메모리 영역을 할당하여 준다.

 

해당 할당된 메모리 영역이 Runtime Data Area이고 Class Loader는 해당 class file을 적절하게 분석해 Data Area에 적재적소에 해당 데이터들을 적재해준다.

 

그러면 Excution Engine이 해당 메모리에 적재된 바이너리를 기계어로 변경해 명령어 단위로 실행해준다.

여기서 interpreter(명령어 하나 하나 실행) 방식이 있고, JIT(한꺼번에 컴파일해서 한꺼번에 실행)이 있다. 

 

 

이렇게 간단하게 Java 코드가 실행되는 과정을 간단하게 요약해 보았다.

더 자세한 이야기는 우아한Tech의 JVM Stack & Heap 영상을 참조하면 좋을 듯하다.