2020. 11. 17.

1주차: JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가.


https://github.com/whiteship/live-study/issues/1

목표

자바 소스 파일(.java)을 JVM으로 실행하는 과정 이해하기.

학습할 것

  • JVM이란 무엇인가
  • 컴파일 하는 방법
  • 실행하는 방법
  • 바이트코드란 무엇인가
  • JIT 컴파일러란 무엇이며 어떻게 동작하는지
  • JVM 구성 요소
  • JDK와 JRE의 차이


1. JVM이란 무엇인가

JVM(Java Virtual Machine)은 Java와 자바 바이트 코드로 컴파일이 가능한 다른 언어로 쓰인 프로그램이 컴퓨터에서 실행이 가능하도록 해주는 가상 머신 입니다.

Java는 "한 번 작성하면 어디서나 실행"이라는 개념으로 다양한 플랫폼에서 실행되도록 설계되었습니다. 특정 플랫폼 용으로 컴파일되고 실행되는 C++ 같은 언어와 달리 Java 소스 코드는 먼저 .class 파일 인 바이트 코드로 컴파일 됩니다. 컴파일 된 클래스 파일은 가상 머신에서 해석됩니다.

먼저 자바 소스 코드 (.java 파일)를 바이트 코드 (.class 파일)로 컴파일합니다. 바이트 코드는 자바와 기계어 사이의 중간 언어입니다. 다른 OS나 플랫폼에서 코드를 수정하지 않고도 JVM에서 동일한 바이트 코드를 실행할 수 있습니다.


2. 컴파일 하는 방법

public class FirstJavaProgram {
  public static void main(String[] args){
    System.out.println("This is my first program in java");
  }//End of main
}//End of FirstJavaProgram Class

출처: https://beginnersbook.com/2013/05/first-java-program/

Step 1: 윈도우의 메모장이나 Mac의 'TextEdit'같은 프로그램을 열어 위의 코드를 작성합니다.

Step 2: 우리가 만든 프로그램의 'public class' 이름이 'FirstJavaProgram'이기 때문에 파일 이름을 'FirstJavaProgram.java'로 저장합니다.

Step 3: 윈도우의 'command prompt (cmd)', Mac OS의 Terminal을 열어 아래의 명령어를 작성하고 엔터를 입력합니다.

javac FirstJavaProgram.java

"javac is not recognized as an internal or external command, operable program or batch file" 와 같은 애러 메세지가 보인다면 Path(경로) 설정을 해야 합니다.

윈도우 - cmd에서 java가 설치돼 있는 경로의 bin 폴더 위치를 복사해 아래와 같이 작성합니다.

set path=C:\Program Files\Java\jdk1.8.0_121\bin

Note: jdk 버전이 다를 수 있습니다. 제가 쓰는 자바 버전은 1.8.0_121으로 똑같이 작성했습니다.

Mac OS X - 터미널에서 다음과 같이 작성합니다.

export JAVA_HOME=/Library/Java/Home

터미널에서 다음 명령을 입력해 경로를 확인합니다.

echo $JAVA_HOME

PATH 시스템 변수를 설정하거나 변경하려면 어떻게 해야 합니까?


3. 실행하는 방법

컴파일 후 .class(바이트 코드) 파일을 다음 명령어로 실행합니다.

java FirstJavaProgram


4. 바이트 코드란 무엇인가

자바 바이트 코드는 JVM(Java Virtual Machine)의 명령어 세트(instruction set)입니다.

Java bytecode instruction listings

Java 프로그래머는 Java 바이트 코드를 전혀 인식하거나 이해할 필요가 없습니다. 그러나 IBM developerWorks 저널에서 제안한 것처럼 "바이트 코드와 Java 컴파일러에 의해 생성될 수 있는 바이트 코드를 이해하면 어셈블리에 대한 지식이 C 또는 C ++ 프로그래머에게 도움이 되는 것과 동일한 방식으로 Java 프로그래머에게 도움이 됩니다."
출처: https://www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/


5. JIT 컴파일러란 무엇이며 어떻게 동작하는지

JIT (Just-In-Time) 컴파일러는 런타임시 Java 애플리케이션의 성능을 향상시키는 Java Runtime Environment의 구성 요소입니다. JIT 컴파일러는 시작시 수천개의 메서드를 호출 할 수 있으므로 호출되는 모든 메서드를 컴파일하지는 않습니다. 대신 메서드가 호출 된 횟수를 기록합니다. 개수가 사전 정의된 호출 임계값에 도달하면 JIT 컴파일이 작동합니다. JIT에 의해 메소드가 컴파일되면 VM은 해석(interpreting)하는 대신 컴파일 된 메소드를 호출합니다.

나중에 읽어볼 것
Chapter 4. Working with the JIT Compiler
The JIT compiler


6. JVM 구성 요소

JVM은 크게 세 가지 주요 영역으로 구성됩니다.

  • 클래스 로더 하위 시스템
  • 런타임 데이터 영역
  • 실행 엔진

1. 클래스 로더 서브시스템(ClassLoader Subsystem)

  1. 로딩(Loading)
    • 클래스(Class)를 사용하려고 하면 자바 클래스 로더가 해당 클래스를 메모리에 로드
    • 첫번째 클래스 실행은 일반적으로 정적 main() 메서드를 선언하고 사용
  2. 연결(Linking)
    • 검증(Verify) – 바이트 코드 검증기는 생성된 바이트 코드가 적절한지 여부를 확인하고 확인이 실패하면 확인 오류가 발생.
    • 준비(Prepare) – 모든 정적 변수에 대해 메모리가 할당(allocated)되고 기본값으로 지정(assigned)됨.
    • 해결(Resolve) – 모든 기호 메모리 참조(symbolic memory references)가 Method Area의 원래 참조(original references)로 대체.
  3. 초기화(Initialization)
    • ClassLoading의 마지막 단계입니다. 여기서 모든 정적 변수(static variables)는 원래 값(original values)으로 할당(assigned)되고 정적 블록(static block)이 실행됩니다.

2. 런타임 데이터 영역(Runtime Data Area)

런타임 데이터 영역은 5개의 주요 구성 요소로 나뉩니다.

  1. 메서드 영역(Method Area) – 정적 변수를 포함하여 모든 클래스 수준 데이터가 여기에 저장됩니다. JVM당 하나의 메소드 영역만 있으며 공유자원입니다.
  2. 힙 영역(Heap Area) – 모든 개체와 해당 인스턴스 변수 및 배열이 여기에 저장됩니다. JVM 당 하나의 힙 영역도 있습니다. 메서드 및 힙 영역은 여러 스레드에 대한 메모리를 공유하므로 저장된 데이터는 스레드로부터 안전(thread-safe)하지 않습니다.
  3. 스택 영역(Stack Area) – 모든 스레드는 각자 런타임 스택이 생성됩니다. 모든 메서드 호출에 스택 프레임이라는 불리는 스택 메모리가 만들어집니다. 모든 지역 변수는 스택 메모리에 생성됩니다. 스택 영역은 공유 리소스가 아니므로 스레드로부터 안전합니다. 스택 프레임은 세 가지 하위 항목으로 나뉩니다.
    • 로컬 변수 배열(Local Variable Array) – 얼마나 많은 로컬 변수가 메소드와 연관돼 있는지와 해당 값이 여기에 저장됩니다.
    • 피연산자 스택(Operand stack) – 중간 작업이 필요한 경우 피연산자 스택은 작업을 수행하는 런타임 작업 영역으로 작동합니다.
    • 프레임 데이터(Frame data) – 메소드와 연관된 기호가 여기에 저장됩니다. 예외가 발생하면 캐치 블록(catch block) 정보를 프레임 데이터에 유지합니다.
  4. PC 레지스터(PC Registers) – 각 스레드에는 별도의 PC 레지스터가 있으며, 명령어가 실행되면 현재 실행중인 명령어의 주소를 유지하기 위해 PC 레지스터가 다음 명령(instruction)으로 업데이트됩니다.
  5. 네이티브 메서드 스택(Native Method stacks) – 네이티브 메서드 스택은 네이티브 메서드 정보를 보유합니다. 모든 스레드에 대해 별도의 네이티브 메서드 스택이 생성됩니다.

3. 실행 엔진(Execution Engine)

런타임 데이터 영역(Runtime Data Area)에 할당 된 바이트 코드는 실행 엔진(Execution Engine)에 의해 실행됩니다. 실행 엔진은 바이트 코드를 조각 조각으로 읽고 실행합니다.

  1. 인터프리터(Interpreter) – 인터프리터는 바이트 코드를 더 빨리 해석하지만 느리게 실행됩니다. 인터프리터의 단점은 하나의 메서드가 여러 번 호출 될 때마다 새로운 해석(interpretation)이 필요하다는 것입니다.
  2. JIT 컴파일러 – JIT 컴파일러는 인터프리터의 단점을 무력화합니다. 실행 엔진은 바이트 코드를 변환하는 데 인터프리터를 사용하지만 반복된 코드를 찾으면 전체 바이트 코드를 컴파일하고 네이티브 코드로 변경하는 JIT 컴파일러를 사용합니다. 이 네이티브 코드는 반복되는 메서드 호출에 직접 사용되어 시스템 성능을 향상시킵니다.
    1. 중간 코드 생성기(Intermediate Code Generator)
    2. 코드 최적화(Code Optimizer)
    3. 타겟 코드 생성기(Target Code Generator) – 기계 코드 또는 네이티브 코드 생성 담당
    4. 프로파일러(Profiler) - 여러번 사용되는 메소드 핫스팟을 찾는 역할
  3. 가비지 수집기(Garbage Collector): 참조되지 않은 개체를 수집하고 제거합니다. 가비지 수집은 System.gc()를 호출하여 실행할 수 있지만, 실행이 보장되지는 않습니다. JVM의 가비지 수집기는 생성된 개체(objects)를 수집합니다.

JNI(Java Native Interface): JNI는 Native Method Libraries와 상호 작용하며 실행 엔진에 필요한 Native Libraries를 제공합니다.

Native Method Libraries: 실행 엔진(Execution Engine)에 필요한 네이티브 라이브러리 모음입니다.


7. JDK와 JRE의 차이

JRE(Java Runtime Environment)는 자바 프로그램 실행 환경을 위한 설치 패키지입니다. 자바 프로그램 실행에 필요한 파일들이 포함돼 있지만 개발하지 못합니다.

JDK(Java Development Kit)는 자바 개발에 모든 기능을 갖춘 SDK(Software Development Kit)입니다. JRE에 있는 모든 기능뿐만 아니라 컴파일러(javac)와 개발 도구가 포함돼 있습니다.


https://github.com/whiteship/live-study/issues/1#issuecomment-727216519
1주차 추가 과제 - javac 옵션 조사

-g
지역 변수를 포함한 모든 디버깅 정보를 생성합니다. 기본적으로 행 번호와 소스 파일 정보만 생성됩니다.

-g:none
디버깅 정보를 생성하지 않습니다.

-g:[lines, vars, source],[lines, vars, source],[lines, vars, source]
쉼표로 구분된 키워드 목록 중 지정된 종류의 디버깅 정보만 생성합니다. 유효한 키워드는 다음과 같습니다.

    lines : 줄 번호 디버깅 정보

    vars : 지역 변수 디버깅 정보

    source : 소스 파일 디버깅 정보

-nowarn
경고 메시지를 비활성화합니다. 이 옵션은 -Xlint:none 옵션과 동일하게 작동합니다.

-verbose
컴파일러가 수행하는 작업에 대한 메시지를 출력합니다. 메시지에는 로드된 클래스 및 컴파일된 소스 파일에 대한 정보가 포함됩니다.

-deprecation
더 이상 사용되지 않는(deprecated) 멤버 또는 클래스의 사용 또는 상속(override)에 대한 설명을 표시합니다. -deprecation 옵션이 없으면 javac는 소스 파일의 요약을 표시합니다. -deprecation 옵션은 -Xlint:deprecation의 줄임 표현입니다.

-classpath <path>, --class-path <path>, -cp <path>
사용자의 클래스 파일 및 주석(annotation) 프로세서를 찾을 위치를 지정합니다. 이 클래스 경로는 사용자의 CLASSPATH 환경 변수를 재정의(overrides)합니다.

    --class-path, -classpath 또는 -cp 지정되지 않은 경우, 클래스 경로는 현재 디렉토리입니다.

    -sourcepath 옵션이 지정되어 있지 않은 경우, 사용자 클래스 패스는 소스 파일을 검색합니다.

    -processorpath 옵션이 지정되어 있지 않은 경우, 클래스 패스는 주석(annotation) 프로세서를 검색합니다.

-source-path <path>, -sourcepath <path>
입력 받을 소스 파일의 위치를 지정합니다. 클래스나 인터페이스의 정의를 찾기 위해 사용할 소스 코드 경로입니다. 사용자 class path와 마찬가지로 소스 경로 항목은 Oracle Solaris에서는 콜론(:)으로, Windows에서는 세미콜론(;)으로 구분합니다. Jar archives, Zip archives, 폴더일 수 있습니다. 패키지에 사용되는 경우 디렉토리나 아카이브(archives) 내의 로컬 경로 이름이 패키지 이름을 반영해야 합니다.

-bootclasspath <path>, --boot-class-path <path>
부트스트랩(bootstrap) 클래스 파일의 위치를 재정의합니다.

-extdirs <dirs>
설치된 확장 파일의 경로를 재정의합니다. 디렉토리 변수는 콜론으로 구분된 디렉토리 목록입니다. 지정된 특정 경로의 JAR 파일을 검색해 클래스 파일을 찾습니다. 발견한 모든 JAR 파일은 클래스 경로의 일부가 됩니다.

-endorseddirs <dirs>
승인된 표준 경로의 위치를 재정의합니다.

-proc: {none,only}, {none,only}
주석 처리(annotation processing)와 컴파일이 수행되는지 여부를 제어합니다. -proc:none 주석 처리없이 컴파일이 수행됨을 의미합니다. -proc:only 후속 컴파일 없이 주석 처리만 수행됨을 의미합니다.

-processor <class1>[,<class2>,>class3>...]
실행할 주석 프로세서의 이름입니다. 기본 검색 프로세스를 무시합니다.

-processorpath <path>, -processor-path <path>
주석 프로세서를 찾을 위치를 지정합니다. 이 옵션을 사용하지 않으면 클래스 경로에서 프로세서를 검색합니다.

-parameters
메소드 매개변수의 리플렉션(reflection)을 위한 메타 데이터를 생성합니다. 생성된 클래스 파일안의 생성자와 메소드의 형식 매개변수(formal parameter) 이름을 저장해 리플렉션 API의 java.lang.reflect.Executable.getParameters 메소드가 가져올 수 있게 합니다.

-d <directory>
클래스 파일의 대상 디렉토리를 설정합니다. 클래스가 패키지의 일부인 경우 javac는 패키지 이름을 반영하는 하위 디렉토리에 클래스 파일을 넣고 필요에 따라 디렉토리를 만듭니다. -d 옵션이 지정되지 않은 경우 javac는 소스파일과 동일한 디렉토리에 저장합니다.

-s <directory>
생성된 소스 파일을 배치할 디렉토리를 지정합니다.

-h <directory>
생성된 기본 헤더 파일을 배치할 위치를 지정합니다.

-implicit:{[none, class],[none, class]}
암시적으로 참조된(implicitly referenced) 파일의 클래스 파일을 생성할지 여부를 지정합니다.

   -implicit:class 자동으로 클래스 파일을 생성합니다.

   -implicit:none 클래스 파일 생성을 금지합니다.

이 옵션을 지정하지 않으면 기본적으로 클래스 파일을 생성합니다. 이 경우 컴파일러는 주석 프로세서를 수행할 때 클래스 파일이 생성되면 경고를 발생합니다. -implicit 옵션이 명확하게 설정된 경우에는 경고가 발생하지 않습니다.

-encoding <encoding>
EUC-KR, UTF-8 같은 소스 파일에서 사용하는 문자 인코딩을 지정합니다. -endocing 옵션을 지정하지 않으면 플랫폼 기본으로 사용됩니다.

-source <release>
허용되는 소스 코드의 버전을 지정합니다.

Note: JDK 9부터 javac는 5보다 작거나 같은 값(버전)을 지원하지 않습니다. 5보다 작거나 같은 값을 사용하는 경우 javac -source 6이 지정된 것처럼 작동합니다.

-target <release>
특성 VM 버전의 클래스 파일을 생성합니다.

-profile <profile>
사용된 API가 지정된 프로필에서 사용 가능한지 확인합니다.

-version
버전 정보를 출력합니다.

-help
표준 옵션의 요약을 출력합니다.

-Akey[=value]
주석 프로세서에 전달할 옵션을 지정합니다. javac에서 직접 해석(interpreted)되지 않지만, 개별 프로세서에서 사용할 수 있습니다. 키값은 점(.)으로 구분된 하나 이상의 식별자입니다.

-X
추가 옵션에 대한 도움말을 출력합니다.

-J<flag>
옵션을 런타임 시스템에 전달합니다. 여기서 옵션은 javacommand에 설명된 옵션 중 하나입니다. 예를 들어 '-J-Xms48m'은 시작 메모리를 48MB로 설정합니다.

-Werror
경고가 발생하면 컴파일을 종료합니다.

@<filename>
파일에서 옵션 및 파일 이름을 읽습니다. javac 명령어의 단순화를 위해 javac 명령어에 대한 인수를 포함하는 하나 이상의 파일을 지정할 수 있습니다.(-J 옵션 제외한) 이를 통해 모든 운영 체제에서 다양한 길이의 javac 명령어를 만들 수 있습니다.

출처: https://docs.oracle.com/javase/9/tools/javac.htm

댓글 없음:

댓글 쓰기