JavaRush /Java Blog /Random-KO /내부적으로 Java 애플리케이션 컴파일 및 실행
Павел Голов
레벨 34
Москва

내부적으로 Java 애플리케이션 컴파일 및 실행

Random-KO 그룹에 게시되었습니다

콘텐츠:

  1. 소개
  2. 바이트코드로 컴파일
  3. 프로그램 컴파일 및 실행의 예
  4. 가상 머신에서 프로그램 실행
  5. JIT(Just-In-Time) 컴파일
  6. 결론
내부적으로 Java 애플리케이션 컴파일 및 실행 - 1

1. 소개

안녕하세요 여러분! 오늘 저는 Java로 작성된 애플리케이션을 실행한 후 JVM(Java Virtual Machine) 내부에서 어떤 일이 발생하는지에 대한 지식을 공유하고 싶습니다. 요즘에는 새로운 개발자가 이러한 중요한 측면을 놓칠 수 있는 JVM 내부, Java 코드 컴파일 및 실행에 대한 생각을 피하는 데 도움이 되는 최신 개발 환경이 있습니다. 동시에, 인터뷰 중에 이 주제에 관한 질문을 자주 받는데, 이것이 제가 기사를 쓰기로 결정한 이유입니다.

2. 바이트코드로 컴파일

내부적으로 Java 애플리케이션 컴파일 및 실행 - 2
이론부터 시작해 보겠습니다. 애플리케이션을 작성할 때 확장자를 가진 파일을 생성 .java하고 그 안에 Java 프로그래밍 언어로 코드를 배치합니다. 사람이 읽을 수 있는 코드가 포함된 파일을 소스 코드 파일 이라고 합니다 . 소스코드 파일이 준비되면 실행해야 합니다! 그러나 단계에서는 인간만이 이해할 수 있는 정보가 포함되어 있습니다. Java는 다중 플랫폼 프로그래밍 언어입니다. 이는 Java로 작성된 프로그램이 전용 Java 런타임 시스템이 설치된 모든 플랫폼에서 실행될 수 있음을 의미합니다. 이 시스템을 JVM(Java Virtual Machine)이라고 합니다. 프로그램을 소스 코드에서 JVM이 이해할 수 있는 코드로 변환하려면 프로그램을 컴파일해야 합니다. JVM이 이해하는 코드는 바이트코드라고 하며 가상 머신이 이후에 실행할 명령 세트를 포함합니다. javac소스코드를 바이트코드로 컴파일하기 위해 JDK(Java Development Kit)에 컴파일러가 포함되어 있습니다 . 컴파일러는 .java프로그램의 소스 코드가 포함된 확장자를 가진 파일을 입력으로 받아들이고, 출력으로 .class가상 머신에서 프로그램을 실행하는 데 필요한 바이트코드가 포함된 확장자를 가진 파일을 생성합니다. 프로그램이 바이트코드로 컴파일되면 가상 머신을 사용하여 실행할 수 있습니다.

3. 프로그램 컴파일 및 실행 예시

Calculator.java2개의 숫자 명령줄 인수를 취하고 추가 결과를 인쇄하는 file 에 포함된 간단한 프로그램이 있다고 가정합니다 .
class Calculator {
    public static void main(String[] args){
        int a = Integer.valueOf(args[0]);
        int b = Integer.valueOf(args[1]);

        System.out.println(a + b);
    }
}
이 프로그램을 바이트코드로 컴파일하기 위해 javac명령줄에서 컴파일러를 사용합니다.
javac Calculator.java
컴파일 후에는 바이트코드가 포함된 파일을 출력으로 받습니다 Calculator.class. 이는 명령줄에서 java 명령을 사용하여 컴퓨터에 설치된 Java 시스템을 사용하여 실행할 수 있습니다.
java Calculator 1 2
파일 이름 뒤에 2개의 명령줄 인수(숫자 1과 2)가 지정되었습니다. 프로그램을 실행한 후 숫자 3이 명령줄에 표시됩니다. 위의 예에서는 자체적으로 존재하는 간단한 클래스가 있었습니다. . 하지만 클래스가 패키지에 포함되어 있으면 어떻게 될까요? 다음 상황을 시뮬레이션해 보겠습니다. 디렉터리를 만들고 src/ru/javarush거기에 클래스를 배치합니다. 이제 다음과 같습니다(파일 시작 부분에 패키지 이름을 추가했습니다).
package ru.javarush;

class Calculator {
    public static void main(String[] args){
        int a = Integer.valueOf(args[0]);
        int b = Integer.valueOf(args[1]);

        System.out.println(a + b);
    }
}
다음 명령을 사용하여 이러한 클래스를 컴파일해 보겠습니다.
javac -d bin src/ru/javarush/Calculator.java
-d bin이 예에서는 컴파일된 파일을 bin디렉토리와 유사한 구조의 디렉토리에 저장하는 추가 컴파일러 옵션을 사용했지만 src디렉토리는 bin미리 생성되어야 합니다. 이 기술은 소스 코드 파일과 바이트코드 파일의 혼동을 피하기 위해 사용됩니다. 컴파일된 프로그램을 실행하기 전에 개념을 설명하는 것이 좋습니다 classpath. Classpath가상 머신이 패키지와 컴파일된 클래스를 찾는 경로입니다. 즉, 이러한 방식으로 파일 시스템의 어떤 디렉토리가 Java 패키지 계층 구조의 루트인지 가상 머신에 알려줍니다. Classpath플래그를 사용하여 프로그램을 시작할 때 지정할 수 있습니다 -classpath. 다음 명령을 사용하여 프로그램을 시작합니다.
java -classpath ./bin ru.javarush.Calculator 1 2
이 예에서는 해당 클래스가 있는 패키지의 이름을 포함하여 클래스의 전체 이름이 필요했습니다. 최종 파일 트리는 다음과 같습니다:
├── src
│     └── ru
│          └── javarush
│                  └── Calculator.java
└── bin
      └── ru
           └── javarush
                   └── Calculator.class

4. 가상 머신에 의한 프로그램 실행

그래서 우리는 서면 프로그램을 시작했습니다. 하지만 컴파일된 프로그램이 가상 머신에 의해 실행되면 어떻게 될까요? 먼저 컴파일과 코드 해석의 개념이 무엇을 의미하는지 알아 보겠습니다. 컴파일은 고급 소스 언어로 작성된 프로그램을 기계 코드와 유사한 저급 언어로 된 동등한 프로그램으로 번역하는 것입니다. 해석은 연산자별(명령별, 줄별) 분석, 처리 및 소스 프로그램 또는 요청의 즉각적인 실행입니다(프로그램을 실행하지 않고 번역하는 컴파일과 반대). 자바 언어에는 컴파일러( javac)와 인터프리터가 있는데, 이는 바이트코드를 한 줄씩 기계어 코드로 변환하고 즉시 실행하는 가상머신이다. 따라서 컴파일된 프로그램을 실행하면 가상 머신은 이를 해석하기 시작합니다. 즉, 바이트 코드를 기계 코드로 한 줄씩 변환하고 실행하는 것입니다. 불행하게도 순수 바이트코드 해석은 다소 긴 프로세스이며 경쟁사에 비해 Java를 느리게 만듭니다. 이를 방지하기 위해 가상 머신의 바이트코드 해석 속도를 높이는 메커니즘이 도입되었습니다. 이 메커니즘을 JITC(Just-in-time 컴파일)이라고 합니다.

5. JIT(Just-In-Time) 컴파일

간단히 말해서 Just-In-Time 컴파일의 메커니즘은 다음과 같습니다. 프로그램에 여러 번 실행되는 코드 부분이 있는 경우 해당 부분을 기계어 코드로 한 번 컴파일하여 향후 실행 속도를 높일 수 있습니다. 프로그램의 이러한 부분을 기계어 코드로 컴파일한 후, 프로그램의 이 부분을 호출할 때마다 가상 머신은 컴파일된 기계어 코드를 해석하지 않고 즉시 실행하므로 자연스럽게 프로그램 실행 속도가 빨라집니다. 프로그램 속도를 높이는 것은 메모리 소비를 늘리고(컴파일된 기계어 코드를 어딘가에 저장해야 합니다!) 프로그램 실행 중 컴파일에 소요되는 시간을 늘려 달성됩니다. JIT 컴파일은 다소 복잡한 메커니즘이므로 먼저 살펴보겠습니다. 바이트코드를 기계어 코드로 JIT 컴파일하는 데는 4가지 레벨이 있습니다. 컴파일 수준이 높을수록 더 복잡해 지지만 동시에 해당 섹션의 실행은 수준이 낮은 섹션보다 빠릅니다. JIT - 컴파일러는 해당 조각이 실행되는 빈도에 따라 각 프로그램 조각에 설정할 컴파일 수준을 결정합니다. 내부적으로 JVM은 2개의 JIT 컴파일러(C1 및 C2)를 사용합니다. C1 컴파일러는 클라이언트 컴파일러라고도 하며 3레벨까지만 코드를 컴파일할 수 있습니다. C2 컴파일러는 네 번째로 가장 복잡하고 빠른 컴파일 수준을 담당합니다.
내부적으로 Java 애플리케이션 컴파일 및 실행 - 3
위에서부터 간단한 클라이언트 애플리케이션의 경우 C1 컴파일러를 사용하는 것이 더 수익성이 높다는 결론을 내릴 수 있습니다. 이 경우 애플리케이션이 얼마나 빨리 시작되는지가 중요하기 때문입니다. 서버 측의 수명이 긴 애플리케이션은 시작하는 데 시간이 더 오래 걸릴 수 있지만 앞으로는 신속하게 작동하고 기능을 수행해야 합니다. 여기서는 C2 컴파일러가 우리에게 적합합니다. -clientJVM의 x32 버전에서 Java 프로그램을 실행할 때 및 플래그 를 사용하여 사용할 모드를 수동으로 지정할 수 있습니다 -server. 이 플래그가 지정되면 -clientJVM은 복잡한 바이트코드 최적화를 수행하지 않으므로 애플리케이션 시작 시간이 빨라지고 소비되는 메모리 양이 줄어듭니다. 플래그를 지정하면 -server복잡한 바이트코드 최적화로 인해 애플리케이션을 시작하는 데 시간이 더 오래 걸리고 기계어 코드를 저장하는 데 더 많은 메모리를 사용하지만 앞으로는 프로그램이 더 빠르게 실행됩니다. x64 버전의 JVM에서는 플래그 -client가 무시되고 기본적으로 애플리케이션 서버 구성이 사용됩니다.

6. 결론

이것으로 Java 애플리케이션의 컴파일 및 실행 방법에 대한 간략한 개요를 마칩니다. 주요 요점:
  1. javac 컴파일러는 프로그램의 소스 코드를 Java 가상 머신이 설치된 모든 플랫폼에서 실행될 수 있는 바이트코드로 변환합니다.
  2. 컴파일 후 JVM은 결과 바이트코드를 해석합니다.
  3. Java 애플리케이션의 속도를 높이기 위해 JVM은 프로그램에서 가장 자주 실행되는 섹션을 기계어 코드로 변환하고 메모리에 저장하는 Just-In-Time 컴파일 메커니즘을 사용합니다.
이 기사가 우리가 가장 좋아하는 프로그래밍 언어의 작동 방식을 더 깊이 이해하는 데 도움이 되었기를 바랍니다. 읽어주셔서 감사합니다. 비판도 환영합니다!
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION