이미 현재 시점에서 8주차 까지 와서 매우 늦었지만
지금이라도 멤버십을 구독하고 스터디 스타트!
자바 소스 파일(.java)을 JVM으로 실행하는 과정 이해하기.
자바 가상 머신(Java Virtual Machine)은 자바로 만들어진 바이트 코드를 실행시키기 위한 가상 머신이다. JVM은 크게 두 가지 역할을 하는데, 자바 프로그램이 어떤 기기, 어떤 운영체제에서 실행되더라도 동일한 기능을 실행하는 역할과, 프로그램의 메모리를 관리하고 최적화 하는 역할을 한다. 기기나 운영체제의 종류와는 상관없이 실행되도록 하는 JVM의 첫 번째 역할로 인해, 자바는 플랫폼 독립적인 프로그래밍 언어가 되었다.
다만 사용자의 하드웨어와 운영체제에 맞는 JVM이 설치되어 있어야 한다. 즉, JVM은 자바 파일을 플랫폼에 독립적으로 사용할 수 있게 해주지만, JVM 자체는 종속적이다.
자바로 프로그램을 작성해서 컴파일 하면 .class 파일이라는 바이트 코드가 생기는데, JVM은 해당 파일을 설치된 OS에 맞는 기계어로 변환한다. 먼저 클래스 로더가 .class 파일을 로드하고, 로드된 파일들은 실행 엔진(Execution Engine)에 의해 기계어로 해석된다. 해석된 바이트코드는 메모리 영역에 위치하여 실행된다.
이렇게 자바의 바이트코드는 JVM 위에서 실행되기 때문에, 동일한 기능의 프로그램을 네이티브 언어로 짠 것 보다 실행 속도가 더 느리다는 단점이 있다.
JDK를 설치하면 javac라는 자바 컴파일러가 함께 설치된다. (윈도우 기준) 명령 프롬프트에 javac -version을 치면 설치된 컴파일러의 버전을 알 수 있다. 이 javac가 자바 소스코드를 바이트코드로 컴파일 해주는 프로그램이다.
터미널에서 자바 소스파일이 있는 디렉토리로 들어가서, javac 파일 이름.java 를 입력하면 소스 코드에 오류가 없을 경우 컴파일러가 .java 파일을 .class 파일로 컴파일한다. 참고로, 시스템 환경 변수로 javac의 경로가 지정되어있지 않다면 시스템에서 javac를 찾을 수 없기 때문에 오류가 난다. 또한 만약 자바 소스 코드가 default 패키지에 들어있지 않다면 파일 이름 앞에 패키지 이름. 을 작성해 주어야 한다.
이클립스, 인텔리제이 같은 IDE에서는 더욱 간단하게 컴파일 할 수 있다. 소스 코드를 작성하고, IDE의 Run 기능을 사용하여 빌드하는 중간에 IDE에서 알아서 컴파일해준다.
주의할 점은 컴파일과 빌드는 다른 과정이다. 컴파일은 작성한 소스 코드를 바이너리 코드로 번역하는 과정이고, 빌드는 실행 가능한 소프트웨어로 만드는 과정이다. 따라서 컴파일이 빌드 과정 안에 포함되어 있다.
컴파일 과정과 마찬가지로 할 수 있다. 컴파일 과정에서는 .java 파일이 있는 디렉토리로 들어가서 javac 명령어를 사용했는데, 이렇게 만들어진 class 파일을 실행 할 때는 명령어는 javac 가 아니라 java 패키지 이름.파일 이름이 된다. 또한 뒤에 .java 확장자를 붙이지 않는다.
마찬가지로 java 환경변수가 등록되어 있지 않다면 오류가 난다. 또한 IDE에서는 Run 버튼만 누르면 컴파일과 실행을 한번에 다 해서 결과를 출력해준다.
바이트 코드(Byte Code)란 특정 하드웨어가 아닌 가상 머신에서 돌아가는 이진 코드다. 즉, 자바 바이트 코드는 JVM이 이해하고 실행할 수 있는 이진 코드가 된다.
개발자가 작성한 java 소스 파일은 컴퓨터가 이해할 수 없는 문법으로 작성되어 있다. 컴퓨터는 이 파일을 해석할 수 없기 때문에 컴퓨터가 해석할 수 있는 기계어로 변환해 주어야 한다. 이때 이 번역 역할을 하는 것이 JVM이다.
그런데 앞서 말했듯이 우리가 작성한 java 소스 파일은 JVM도 이해할 수 없다. 따라서 컴파일 과정을 통해서 .class 파일로 바꿔주는데, 이 .class파일이 JVM이 이해할 수 있는 자바 바이트 코드다. 이렇게 번역된 바이트 코드는 JVM에 의해 실행된다.
위에서 JVM에 바이트 코드가 전달되면, JVM은 바이트 코드를 실행한다고 설명했다. 이 때 바이트 코드를 실행하는 과정에서 JVM은 JIT 컴파일을 통해 바이트 코드를 기계어로 번역한다.
컴퓨터 프로그램을 만드는 방법은 크게 두 가지가 있는데, 정적 컴파일 방식과 인터프리트 방식이다. 정적 컴파일 방식의 대표적인 예인 C나 C++에서는 프로그램을 실행하기 전에 한 번에 컴파일을 한다. 반면 자바스크립트 같은 인터프리트 언어들은 프로그램을 실행할 때 코드를 읽어가면서 그에 대응하는 기계어 코드를 생성한다.
일반적인 인터프리터 언어는 바이트코드나 소스코드를 최적화 과정이 없기 번역하기 때문에 성능이 낮고, 정적 컴파일 언어는 실행 전에 무조건 컴파일을 해야하기 때문에 다양한 플랫폼에 맞게 컴파일을 하려면 시간이 오래 걸린다.
JIT 컴파일(Just-In-Time compilation)은 이 두 가지 방법을 혼합한 것으로, JIT 컴파일러는 바이트 코드를 읽어서 빠르게 기계어로 변환한다. JIT 컴파일러는 프로그램을 실행하는 과정에서 필요한 부분만 기계어로 변환하고, 기계어로 변환된 부분은 캐시에 저장해서 재사용시 컴파일을 다시 할 필요가 없다. 때문에 일반적인 인터프리트 방식보다 훨씬 속도가 빠르다.
자바는 J2SE(현재의 Java SE) 1.2 버전에서 처음 JIT 컴파일을 도입했고, SE 1.3부터는 기본으로 JIT 컴파일러를 탑재했는데, JIT 컴파일러의 도입으로 자바의 컴파일 속도는 비약적으로 상승했다.
JVM의 구성 요소를 크게 나누자면 다음과 같은 요소로 구성된다.
클래스 로더는 JVM으로 클래스 파일을 로드하고 배치하는 모듈이다. 자바는 컴파일 타임에 필요한 클래스를 로드하지 않고, 런타임에 클래스를 처음 참조할 때 해당 클래스를 로드하고 링크한다. (동적 로드)
실행 엔진은 메모리 영역에 로드된 바이트 코드를 실행하는 역할을 한다. 이 때 자바는 앞서 설명했던 JIT 컴파일 방식으로 바이트 코드를 실행한다.
자바는 메모리를 Fortran이나 C 처럼 사용자가 수동으로 할당 및 해제 하지 않고, JVM이 쓰이지 않는 메모리를 자동으로 찾아서 해제한다. 이를 가비지 컬렉션이라고 하는데, 이 과정을 실행하는 모듈이 바로 가비지 콜렉터다.
마지막으로 런타임 데이터 영역은 JVM의 메모리 영역인데, JVM이라는 프로그램이 실행되면 운영체제로부터 할당받는 메모리 영역이다. 크게 PC 레지스터, 메소드 영역, JVM 스택, 힙, 네이티브 메소드 스택으로 구성된다.
JRE(Java Runtime Environment)는 자바 런타임 환경, JDK(Java Development Kit)는 자바 개발 도구다.
JRE는 자바 프로그램을 실행하기 위한 최소한의 환경 도구로, JRE를 사용해서 자바 프로그램을 개발 할 수는 없다.
반면 JDK는 사용자가 자바 프로그램을 개발하기 위한 도구로, JRE가 제공하는 자바 실행 환경 뿐 아니라, 자바 개발에 필요한 여러가지 명령어와 자바 컴파일러를 제공한다.
즉, 자바 프로그램을 실행하기 위해서는 JRE만 설치해도 되지만, 자바 프로그램을 직접 개발하기 위해서는 반드시 JDK를 설치해야 한다.
참고 자료
https://m.blog.naver.com/ki630808/221844888233
https://odol87.tistory.com/5
https://sowells.tistory.com/128