자바 가상 머신-JIT 컴파일러
이 장에서는 JIT 컴파일러와 컴파일 된 언어와 해석 된 언어의 차이점에 대해 알아 봅니다.
컴파일 된 언어와 해석 된 언어
C, C ++ 및 FORTRAN과 같은 언어는 컴파일 된 언어입니다. 그들의 코드는 기본 컴퓨터를 대상으로하는 바이너리 코드로 제공됩니다. 즉, 기본 아키텍처를 위해 특별히 작성된 정적 컴파일러에 의해 한 번에 상위 수준 코드가 이진 코드로 컴파일됩니다. 생성 된 바이너리는 다른 아키텍처에서 실행되지 않습니다.
반면에 Python 및 Perl과 같은 번역 언어는 유효한 인터프리터가있는 한 모든 컴퓨터에서 실행할 수 있습니다. 상위 수준 코드를 한 줄씩 살펴보고 이진 코드로 변환합니다.
해석 된 코드는 일반적으로 컴파일 된 코드보다 느립니다. 예를 들어 루프를 고려하십시오. 인터 프리트는 루프가 반복 될 때마다 해당 코드를 변환합니다. 반면에 컴파일 된 코드는 번역을 하나만 만듭니다. 또한 인터프리터는 한 번에 한 줄만 볼 수 있기 때문에 컴파일러와 같은 명령문 실행 순서 변경과 같은 중요한 코드를 수행 할 수 없습니다.
아래에서 이러한 최적화의 예를 살펴 보겠습니다.
Adding two numbers stored in memory. 메모리 액세스는 여러 CPU 사이클을 소비 할 수 있으므로 좋은 컴파일러는 메모리에서 데이터를 가져 와서 데이터를 사용할 수있을 때만 추가를 실행하라는 명령을 내립니다. 기다리지 않고 그 동안 다른 명령을 실행합니다. 반면에 인터프리터가 주어진 시간에 전체 코드를 인식하지 못하기 때문에 해석 중에 그러한 최적화는 불가능합니다.
그러나 통역 언어는 해당 언어의 유효한 통역사가있는 모든 컴퓨터에서 실행될 수 있습니다.
Java가 컴파일되거나 해석됩니까?
자바는 중간 지점을 찾으려고했습니다. JVM이 javac 컴파일러와 기본 하드웨어 사이에 있기 때문에 javac (또는 다른 컴파일러) 컴파일러는 플랫폼 별 JVM이 이해하는 바이트 코드에서 Java 코드를 컴파일합니다. 그런 다음 JVM은 코드가 실행될 때 JIT (Just-in-time) 컴파일을 사용하여 바이너리로 Bytecode를 컴파일합니다.
핫스팟
일반적인 프로그램에는 자주 실행되는 코드의 작은 부분 만 있으며, 전체 애플리케이션의 성능에 큰 영향을 미치는 것은이 코드입니다. 이러한 코드 섹션을HotSpots.
코드의 일부 섹션이 한 번만 실행되면 컴파일하는 것은 노력 낭비이며 대신 바이트 코드를 해석하는 것이 더 빠릅니다. 그러나 섹션이 핫 섹션이고 여러 번 실행되면 JVM이 대신 컴파일합니다. 예를 들어 메서드가 여러 번 호출되는 경우 코드를 컴파일하는 데 걸리는 추가주기는 생성되는 더 빠른 바이너리에 의해 오프셋됩니다.
또한 JVM이 특정 메소드 또는 루프를 더 많이 실행할수록 더 빠른 바이너리가 생성되도록 잡다한 최적화를 수행하기 위해 더 많은 정보를 수집합니다.
다음 코드를 살펴 보겠습니다.
for(int i = 0 ; I <= 100; i++) {
System.out.println(obj1.equals(obj2)); //two objects
}
이 코드가 해석되면 인터프리터는 obj1의 클래스를 반복 할 때마다 추론합니다. 이는 Java의 각 클래스에 .equals () 메서드가 있기 때문입니다. 이는 Object 클래스에서 확장되고 재정의 될 수 있습니다. 따라서 obj1이 각 반복의 문자열 인 경우에도 추론은 계속 수행됩니다.
반면에 실제로 일어날 일은 JVM이 각 반복에 대해 obj1이 String 클래스이므로 String 클래스의 .equals () 메서드에 해당하는 코드를 직접 생성한다는 것을 JVM이 알아 차리는 것입니다. 따라서 조회가 필요하지 않으며 컴파일 된 코드가 더 빨리 실행됩니다.
이러한 종류의 동작은 JVM이 코드 동작 방식을 알고있는 경우에만 가능합니다. 따라서 코드의 특정 섹션을 컴파일하기 전에 대기합니다.
아래는 또 다른 예입니다.
int sum = 7;
for(int i = 0 ; i <= 100; i++) {
sum += i;
}
인터프리터는 각 루프에 대해 메모리에서 'sum'의 값을 가져 와서 'I'를 추가하고 다시 메모리에 저장합니다. 메모리 액세스는 비용이 많이 드는 작업이며 일반적으로 여러 CPU주기가 필요합니다. 이 코드는 여러 번 실행되므로 HotSpot입니다. JIT는이 코드를 컴파일하고 다음과 같이 최적화합니다.
'sum'의 로컬 사본은 특정 스레드에 특정한 레지스터에 저장됩니다. 모든 작업은 레지스터의 값으로 수행되고 루프가 완료되면 값이 메모리에 다시 기록됩니다.
다른 스레드도 변수에 액세스하면 어떻게됩니까? 다른 스레드가 변수의 로컬 복사본을 업데이트하기 때문에 오래된 값을 보게됩니다. 이러한 경우 스레드 동기화가 필요합니다. 매우 기본적인 동기화 프리미티브는 'sum'을 휘발성으로 선언하는 것입니다. 이제 변수에 액세스하기 전에 스레드는 로컬 레지스터를 플러시하고 메모리에서 값을 가져옵니다. 액세스 후 값은 즉시 메모리에 기록됩니다.
다음은 JIT 컴파일러가 수행하는 몇 가지 일반적인 최적화입니다.
- 메서드 인라인
- 데드 코드 제거
- 호출 사이트 최적화를위한 휴리스틱
- 일정한 접기