🍎 Backend/JAVA

[Java] 컴파일 과정

밈98 2023. 12. 4. 12:00

1. 자바의 컴파일과 실행과정

 

1. 작성된 소스코드(.class)를 자바 컴파일러(javac)가 JVM이 이해할 수 있는 바이트 코드로 변환

2. 컴파일된 바이트 코드를 JVM내부의 클래스 로더가 가져와 동적 로딩을 통해 JVM메모리에 적재

3. JVM메모리에 적재된 바이트 코드를 실행엔진을 통해 실행

 


2. 클래스 로더

자바는 동적 로드, 즉 컴파일 타임이 아닌 런타임(바이트 코드)를 실행할때 클레스에 로드하고 링크

동적로드를 담당하는 부분이 JVM의 클래스 로더 이다.

 

- 클래스 로더는 런타임 중에 JVM 메소드 영역에 동적으로 Java 클래스를 로드하는 역할

- 클래스 로더에는 로딩,링크,초기화 단계로 나눠짐

 

  • 로딩
    • 자바 바이트 코드(.class)를 메소드 영역에 저장
    • 로드된 클래스 비롯 부모 클래스 정보
    • 클래스 파일 Class, Interface, Enum 관련 여부
    • 변수나 메소드 정보
  • 링크
    • 검증 : 읽어들인 클래스가 자바 언어 명세 및 JVM 명세에 명시된 대로 잘 구성되어 있는지 검사
    • 준비 : 클래스가 필요로 하는 메모리 할당, 클래스에 정의된 필드, 메소드, 인터페이스를 나타내는 데이터 구조를 준비
    • 분석 : 심볼릭 메모리 레퍼런스를 메소드 영역에 있는 실제 레퍼런스로 교체
  • 초기화
    • 클래스 변수를 적절한 값으로 초기화 한다, static필드들이 설정된 값으로 초기화

2.1 클래스 로더의 종류

  • 클래스 로더의 동작 방식
  • JVM메소드 영역에 클래스가 로드되어 있는지 확인한다. 만일 로드 되어 있는 경우 해당 클래스를 사용함
  • 메소드 영역에 클래스가 로드되어 있지 않는 경우, 시스템 클래스 로더에 클래스 로드를 요청한다
  • 확장 클래스 로더는 부트스트랩 클래스 로더에 요청을 위임한다
  • 부트스트랩 클래스 로더는 부트스르탭 Classpath(JDK/JRE/LIB)에 해당 클래스가 있는지 확인한다. 클래스가 존재하지 않는 경우 확장클래스로더에게 요청을 넘김
  • 확장 클래스 로더는 ClassPath(JDK/JRE/LIB/EXT)에 해당 클래스가 있는지 확인한다. 존재하지 않을 경우 시스템 클래스 로더에게 요청을 넘긴다
  • 시스템 클래스 로더는 시스템 ClassPath에 해당 클래스가 있는지 확인한다. 없을 경우 ClassNotFoundException을 발생시킨다.

2.2 클래스 로더가 지켜야 할 세가지 원칙

  • 위임 원칙
    • 클래스 로더는 클래스 또는 리소스를 찾기 위해 요청을 받았을때, 상위 클래스 로더에게 책임을 위임하는 위임모델을 따른다
  • 가시 범위 원칙
    • 하위 클래스 로더는 상위 클래스 로더가 로드한 클래스를 볼 수 있지만, 반대는 알 수 없다
  • 유일성의 원칙
    • 하위 클래스 로더가 상위클래스 로더에게 로드한 클래스를 다시 로드하지 않아야하는 원칙이다
    • 위임 원칙에 의해 위쪽으로 책임을 위임하기 때문에 고유한 클래스를 보장할 수 있다

2.3 동적 클래스 로딩

JAVA의 클래스 로딩은 클래스 참조 시점에 JVM에 코드가 링크되고, 실제 런타임 시점에 로딩되는 동적 로딩을 거친다.

런타임에 동적으로 클래스를 로딩한다는 것은 JVM이 미리 모든 클래스에 대한 정보를 메소드 영역에 로딩하지 않는다는 것을 의미한다.

 

로드 타임 동적 로딩 (Load-time Dynamic Loading)

public class HelloWorld { 
        public static void main(String[] args) { 
                System.out.println("안녕하세요!"); 
        } 
}

 

위 코드의 경우 다음과 같이 동작한다.

 

  1. JVM이 시작되고 부트스트랩 클래스 로더가 생성된 후에 모든 클래스가 상속받고 있는 Object 클래스를 읽어온다.
  2. 클래스 로더는 명령 행에서 지정한 HelloWorld 클래스를 로딩하기 위해, HelloWorld.class 파일을 읽는다.
  3. HelloWorld 클래스를 로딩하는 과정에서 필요한 클래스인 java.lang.String과 java.lang.System을 로딩한다.

런타임 동적 로딩 (Run-time Dynamic Loading)

public class RuntimeLoading { 
        public static void main(String[] args) { 
                try { 
                        Class cls = Class.forName(args[0]); 
                        Object obj = cls.newInstance(); 
                        Runnable r = (Runnable) obj; 
                        r.run(); 
                } catch (Exception e) {
                        e.printStackTrace();
                }
        }
}

위 코드에서 Class.forName(className) 은 파라미터로 받은 className에 해당하는 클래스를 로딩한 후에 객체를 반환하며, 다음과 같이 동작한다.

 

  1. Class.forName() 메소드가 실행되기 전까지는 RuntimeLoading 클래스에서 어떤 클래스를 참조하는지 알 수 없다.
  2. 따라서 RuntimeLoading 클래스를 로딩할 때는 어떤 클래스도 읽어오지 않고, RuntimeLoading 클래스의 main() 메소드가 실행되고 Class.forName(args[0]) 를 호출하는 순간에 비로소 args[0] 에 해당하는 클래스를 로딩한다.

 

이처럼 클래스를 로딩할 때가 아닌, 코드를 실행하는 순간에 클래스를 로딩하는 것을 런타임 동적 로딩이라고 한다.

 

2.4. 동적로딩의 장점과 단점

장점

  • 런타임 전 까지 메모리 낭비를 막을 수 있다.
    • 사실 런타임 전까지, 해당 클래스가 사용되는 시점까지 정보를 알 수 없다.

단점

  • 런타임에러를 조기에 발견하기 어렵다.
    • 실행문이 실행되는 시점까지 가서야 발견할 수 있다.
  • 동적로딩에도 시간비용이 발생하기때문에 성능저하로 이어질 수 있다.

3. JVM 실행 엔진

- 인터프리터

- JIT(Just in time)

 

3.1 인터프리터(Interpreter)

JVM인터프리터는 런타임중에 바이트 코드를 한 라인씩 읽고 실행한다. 

이 부분에서 속도 문제가 발생

바이트 코드 역시 기계어로 변환되어야 하기 때문에 C, C++ 처럼 미리 컴파일을 통해 기계어로 변경되는 언어에 비해 속도가 느려집니다. 반복문 같은 경우 컴파일 언어와 다르게 인터프리터는 코드 각 줄을 매번 읽고, 번역해야 합니다.

 

3.2 JIT

인터프리터의 속도 문제를 해결하기 위해 디자인 된 기능입니다. 개념을 간단하게 설명하면 '자주 실행되는 바이트 코드 영역을 런타임 중에 기계어로 컴파일하여 사용한다.' 라고 볼 수 있습니다.

  • 바이트코드 중 빈번하게 접근하는 영역에 대해 런타임중 기계어로 컴파일 하고 컴파일 된 명령문을 캐싱해 같은 부분이 호출되면 캐싱된 데이터로 실행 한다.
  • 이 때, JIT 컴파일 과정은 별도의 스레드에서 진행된다.

Q. JIT 컴파일러는 메소드 호출 시 마다 컴파일 하나요?

  • A1. 기본적으로 메소드의 첫 호출 시점에는 컴파일 하지 않는다. JIT 컴파일 과정은 메모리와 시간을 소비하고 JVM이 실행되는 시점에 많은 수의 메서드호출이 이루어지는데 이런 메서드 모두를 바로 컴파일해서 캐싱한다면 성능 저하가 발생 할 수 있다.
  • A2. JVM은 미리 정의된 컴파일 임계값을 가지는데 바이트코드의 특정 영역에 대한 호출 횟수가 컴파일 임계값을 넘길때 JIT 컴파일을 진행한다.

컴파일 임계치

다음의 2가지를 합친 것을 말한다.

  • method entry counter (JVM 내에 있는 메서드가 호출된 횟수)
  • back-edge loop counter (메서드가 루프를 빠져나오기까지 돈 횟수)

두 카운터의 합계를 확인하고 메서드가 컴파일될 자격이 있는지 결정한다.
메서드가 컴파일 될 자격이 있다면 해당 메서드는 컴파일되기 위해 큐에서 대기하게 된다.

그러면 이 메서드들은 이후 컴파일 스레드에 의해 컴파일 되는 것이다.

 

'🍎 Backend > JAVA' 카테고리의 다른 글

[java] abstract(추상) vs implements의 비교  (1) 2024.12.20
[JAVA] 해시 맵  (0) 2023.12.11
[JAVA] 메모리 관리 & call by value  (0) 2023.12.08
자바 기초  (0) 2023.03.02
[JAVA] 클래스와 객체  (0) 2023.02.05