평소처럼 Facebook에서 '생활코딩' 그룹의 게시글을 눈팅하고 있던 도중, 유튜브에서 Java 강의를 주로 하시는 '백기선'님의 글이 눈에 들어왔다. Java 스터디를 진행하신다는 글이였다. 고민할 것도 없이 곧바로 참여한다.
1주차 과제: JVM은 무엇이며, 자바 코드는 어떻게 실행하는 것인가.
- JVM이란 무엇인가
- 컴파일 하는 방법
- javac 옵션
- 실행하는 방법
- 바이트코드란 무엇인가
- JIT 컴파일러란 무엇이며 어떻게 동작하는가
- JVM 구성 요소
- JDK와 JRE의 차이
JVM은 Java Virtual Machine의 줄임말로, Java와 OS 사이에 존재하는 가상머신이다. Java(*.java)를 실행하려면 아래와 같은 과정이 필요하다.
즉, Java Byte Code(*.class)로 변환하여 실행해야 하는데, 이때 변환된 Java Byte Code를 OS에 맞게 해석하는 역할을 하는게 JVM이다.
JVM은 메모리 관리에도 관여한다. JVM은 GC(Garbage Collection)을 이용해 메모리를 관리하여 까다로운 메모리 영역 관리를 보다 손쉽게 할 수 있다. (사용자가 할일은 아무것도 없다. JVM이 알아서한다.)
C, C++와 같은 언어는 JVM과 같은 역할을 해주는 주체가 없어서 직접 free() 메서드를 통해 할당된 메모리를 해제해줘야 하지만, Java는 JVM의 GC(Garbage Collection)을 이용해 오브젝트가 필요하지 않은 시점에 알아서 free()를 실행한다.
컴파일이란 무엇일까?
Compile이란, 사용자가 작성한 코드를 컴퓨터가 이해할 수 있는 기계어로 변환하는 과정으로, 목적파일이 생성된다. (자바의 경우 *.class)
Java는 JDK에 포함되어 있는 javac을 통해 컴파일 하며 아래와 같이 사용한다. (javac을 정상적으로 사용하려면 환경변수 설정이 되어 있어야 한다)
> javac <option> <filename.java>
컴파일이 완료되면, 목적파일인 <filename.class>가 생성된다.
목적파일을 이용해 코드를 실행할 수 있다.
컴파일을 통해 생성된 <filename.class>을 이용해 실행할 수 있다.
> java <filename>
(출처: https://javakong.tistory.com/132)
Java 코드의 실행 과정을 한눈에 파악할 수 있다.
개발에는 InteliJ와 같은 IDE를 사용하다 보니, javac은 익숙치 않은 것 같다.
javac의 옵션에 대해 알아보자.
-classpath
: 컴파일러가 컴파일 과정 중 참조할 클래스 파일들을 찾기 위해 컴파일 시 파일 경로를 지정한다.
-d
: 클래스 파일을 생성할 루트 디렉터리를 지정한다.
(Default --> 소스파일이 위치한 디렉터리)
-encoding
: 소스 파일에 사용된 문자열 인코딩을 설정한다.
(Default --> 플랫폼의 기본적인 컨버터 사용)
-g
: 모든 디버깅 정보를 생성시킨다.
-g:none
: 모든 디버깅 정보를 생성시키지 않는다.
-nowarn
: 경고메세지를 생성시키지 않는다.
-verbose
: 컴파일러와 링커가 현재 어느 소스 파일이 컴파일 중인지, 링크 중인지에 대한 정보를 표시한다.
-deprecation
: 소스 코드내에서, 사용된 deprecated API의 위치를 출력한다.
-sourcepath
: 소스파일의 위치를 지정한다.
-target
: 지정된 자바버전의 가상머신에서 작동 되도록 클래스파일을 생성한다.
> javac -target 1.2 Helloworld.java
-bootclasspath (path)
: 특정한 bootstrap or 확장 클래스를 지정한다.
cross-compile을 가능하게 한다. (주로 모바일에서 사용)
extdirs (디렉터리)
: 특정한, 확장 디렉터리를 지정한다.
cross-compiling에서 주로 사용하며 :
을 통해 디렉터리를 구분한다.
바이트코드의 일반적인 정의는 다음과 같다.
바이트코드(byte code)는 특정 하드웨어가 아닌 가상 머신에서의 실행 프로그램을 위한 이진 표현법으로, 하드웨어가 아닌 소프트웨어에 의해 처리되기 때문에, 보통 기계어보다 더 추상적이다.
Java에서의 바이트코드는, 자바 가상 머신(JVM)이 이해할 수 있는 언어로 변환된 자바 소스 코드를 의미한다.
자바 컴파일러에 의해 변환되는 코드의 명령어 크기가 1바이트라서 자바 바이트 코드라고 불리며, 자바 바이트 코드의 확장자는 .class
다.
자바 바이트 코드는 자바 가상 머신만 설치되어 있으면, 어떤 운영체제에서라도 실행될 수 있다.
JIT 컴파일러란, Just-In-Time 컴파일러의 줄임말로, 동적 번역이라고도 한다.
Just-In-Time은 실시간성 정도의 의미를 가지며, JIT 컴파일러란 다음과 같다.
프로그램을 실제 실행하는 시점에 기계어로 번역하는 컴파일 기법
Java 파일이 실행 되기 위해서 자바 바이트코드는 실시간으로 기계어로 번역되는데, 이와 같은 역할을 JIT 컴파일러가 수행한다. JIT는 JVM의 일부로 동작하면서 자바 바이트코드를 필요한 만큼 쪼개어 실시간으로 컴파일한다.
컴파일된 기계어 코드를 직접 실행해 인터프리터의 느린 성능을 개선한다.
JIT 컴파일러를 통한 컴파일 과정은 바이트 코드를 바로 네이티브 코드로 만드는 것이 아니라 안에서 IR(Intermediate Representation)로 변환하여 최적화를 수행하고 그 다음에 네이티브 코드로 변환는 과정을 거친다.
JIT 컴파일러의 구동 초기에는 바이트코드를 컴파일하는 데 시간과 메모리를 소모하기 때문에 정적 컴파일된 C, C++과 같은 프로그램에 비해 초기 실행 속도와 메모리 사용량에서 손해를 볼 수 있다.
JVM은 크게 4가지 요소로 구성된다.
- 자바 인터프리터 (interpreter)
- 클래스 로더 (class loader)
- JIT 컴파일러 (Just-In-Time compiler)
- 가비지 컬렉터 (garbage collector)
(출처: https://medium.com/javarevisited/java-virtual-machine-internals-class-loader-eea706eb37d9)
자바 인터프리터` : 컴파일이 완료된 자바 바이트 코드를 읽고 해석한다.
클래스 로더: 동적으로 수행되는 자바는 프로그램이 실행중인 Run-Time
이어야 모든 코드가 JVM과 연결된다. 동적으로 클래스를 로딩하여 정상적인 환경을 구성해주는 역할을 한다.
JIT 컴파일러: Run-Time
중 기계어로 변환해주는 컴파일러를 의미한다.
동적 번역이라고도 불리며, 프로그램 실행 속도를 향상시키기 위해 사용한다.
즉, 자바 컴파일러가 생성한 자바 바이트 코드를 즉시 기계어로 변환한다.
가비지 컬렉터: 1번에서 말한 것 처럼, 더 이상 사용하지 않는 메모리를 `자동으로 회수하여 개발자가 따로 메모리를 관리해야 하는 어려움을 없앤다. (Java와 달리 C, C++ 등은 개발자가 직접 관리해야한다.)
JDK란, Java Development Kit의 줄임말로, Java 환경에서 돌아가는 프로그램들을 개발하는 데 필요한 도구들을 모아놓은 소프트웨어 패키지다.
즉, Java 프로그래밍을 하기 위해 JDK는 필수 설치 항목이다.
JDK에는 아래와 같은 것들이 포함되어 있다.
JRE란, Java Runtime Environment의 줄임말로, <filename.class> 파일을 JVM으로 로딩시키는 역할을 한다. 쉽게 말하자면, Java 파일이 실행될 수 있는 환경이다.
즉, JRE만 있으면 Java 프로그램의 실행은 가능하지만, 개발은 불가능하다.
JDK와 JRE의 가장 큰 차이점은 개발을 할 수 있느냐 / 없느냐 인 것 같다.