JavaRush /Java Blog /Random-KO /JVM에 클래스가 로드되는 방식
Aleksandr Zimin
레벨 1
Санкт-Петербург

JVM에 클래스가 로드되는 방식

Random-KO 그룹에 게시되었습니다
프로그래머의 작업 중 가장 어려운 부분이 완료되고 "Hello World 2.0" 애플리케이션이 작성된 후에 남은 것은 배포 키트를 조립하여 고객에게 전달하거나 적어도 테스트 서비스에 전달하는 것입니다. 배포판에서는 모든 것이 원래대로이며 프로그램을 시작하면 Java Virtual Machine이 등장합니다. 가상 머신이 클래스 파일에 바이트코드 형식으로 표시된 명령을 읽고 이를 프로세서에 대한 명령으로 변환한다는 것은 비밀이 아닙니다. 나는 가상 머신에 들어가는 바이트코드의 체계에 대해 조금 이해할 것을 제안합니다.

클래스 로더

.class이는 일반적으로 확장자를 가진 파일에 저장 되지만 네트워크를 통해 다운로드하거나 애플리케이션 자체에서 생성되는 등 다른 소스에서 얻을 수도 있는 컴파일된 바이트코드를 JVM에 제공하는 데 사용됩니다 . JVM에 클래스가 로드되는 방법 - 1Java SE 사양에 따르면 JVM에서 코드를 실행하려면 다음 세 단계를 완료해야 합니다.
  • 리소스에서 바이트코드 로드 및 클래스 인스턴스 생성Class

    여기에는 이전에 로드된 클래스 중에서 요청된 클래스 검색, 로드를 위한 바이트코드 획득 및 정확성 확인, 클래스 인스턴스 생성 Class(런타임에 작업하기 위한) 및 상위 클래스 로드가 포함됩니다. 상위 클래스와 인터페이스가 로드되지 않은 경우 해당 클래스는 로드되지 않은 것으로 간주됩니다.

  • 바인딩(또는 연결)

    사양에 따르면 이 단계는 세 가지 단계로 더 나뉩니다.

    • 확인 - 수신된 바이트코드의 정확성을 확인합니다.
    • 준비 , 정적 필드에 RAM을 할당하고 기본값으로 초기화합니다(이 경우 명시적인 초기화는 초기화 단계에서 이미 발생합니다).
    • 분석 , 유형, 필드 및 메소드의 심볼릭 링크 분석.
  • 수신된 객체 초기화

    여기서는 이전 단락과 달리 무슨 일이 일어나야하는지 모든 것이 명확한 것 같습니다. 물론 이것이 어떻게 일어나는지 정확히 이해하는 것은 흥미로울 것입니다.

이러한 모든 단계는 다음 요구 사항에 따라 순차적으로 수행됩니다.
  • 링크하기 전에 클래스가 완전히 로드되어야 합니다.
  • 클래스는 초기화되기 전에 완전히 테스트되고 준비되어야 합니다.
  • 링크 확인 오류는 연결 단계에서 감지되었더라도 프로그램 실행 중에 발생합니다.
아시다시피 Java는 클래스의 게으른(또는 게으른) 로딩을 구현합니다. 즉, 로드된 클래스의 참조 필드 클래스 로드는 애플리케이션이 해당 클래스에 대한 명시적 참조를 만날 때까지 수행되지 않습니다. 즉, 기호 링크 확인은 선택 사항이며 기본적으로 발생하지 않습니다. 그러나 JVM 구현에서는 에너지 클래스 로딩을 사용할 수도 있습니다. 모든 심볼릭 링크는 즉시 고려되어야 합니다. 이 시점에 마지막 요구 사항이 적용됩니다. 또한 심볼릭 링크의 해결은 클래스 로딩의 어떤 단계에도 연결되지 않는다는 점도 주목할 가치가 있습니다. 일반적으로 이러한 각 단계는 좋은 연구에 도움이 됩니다. 첫 번째 단계, 즉 바이트코드를 로드하는 방법을 알아보도록 하겠습니다.

Java 로더 유형

Java에는 세 가지 표준 로더가 있으며, 각 로더는 특정 위치에서 클래스를 로드합니다.
  1. Bootstrap은 Primordial ClassLoader라고도 불리는 기본 로더입니다.

    rt.jar 아카이브에서 표준 JDK 클래스를 로드합니다.

  2. 확장 ClassLoader – 확장 로더.

    기본적으로 jre/lib/ext 디렉토리에 있지만 java.ext.dirs 시스템 속성으로 설정할 수 있는 확장 클래스를 로드합니다.

  3. 시스템 클래스 로더 – 시스템 로더.

    CLASSPATH 환경 변수에 정의된 애플리케이션 클래스를 로드합니다.

Java는 루트가 기본 클래스인 클래스 로더의 계층 구조를 사용합니다. 다음은 확장 로더이고 그 다음은 시스템 로더입니다. 당연히 각 로더는 자신이 이를 수행할 수 없는 경우에 로드를 위임할 수 있도록 상위에 대한 포인터를 저장합니다.

추상 클래스 ClassLoader

기본 로더를 제외한 각 로더는 추상 클래스의 자손입니다 java.lang.ClassLoader. 예를 들어 확장 로더의 구현은 클래스이고 sun.misc.Launcher$ExtClassLoader시스템 로더는 입니다 sun.misc.Launcher$AppClassLoader. 기본 로더는 기본 로더이며 해당 구현은 JVM에 포함됩니다. 확장되는 모든 클래스는 java.lang.ClassLoader블랙잭 및 이와 동일한 클래스를 로드하는 고유한 방법을 제공할 수 있습니다. 이를 위해서는 해당 방법을 재정의해야 하는데 현재로서는 피상적으로만 고려할 수 있습니다. 이 문제를 자세히 이해하지 못했습니다. 여기 있습니다:
package java.lang;
public abstract class ClassLoader {
    public Class<?> loadClass(String name);
    protected Class<?> loadClass(String name, boolean resolve);
    protected final Class<?> findLoadedClass(String name);
    public final ClassLoader getParent();
    protected Class<?> findClass(String name);
    protected final void resolveClass(Class<?> c);
}
loadClass(String name)클래스를 로드하기 위한 진입점인 몇 가지 공개 메서드 중 하나입니다. loadClass(String name, boolean resolve)구현은 재정의가 필요한 또 다른 보호된 메서드를 호출하는 것으로 요약됩니다 . 이 보호된 메소드의 Javadoc을 보면 다음과 같은 내용을 이해할 수 있습니다. 두 개의 매개변수가 입력으로 제공됩니다. 하나는 로드해야 하는 클래스의 바이너리 이름(또는 정규화된 클래스 이름)입니다. 클래스 이름은 모든 패키지 목록과 함께 지정됩니다. 두 번째 매개변수는 심볼릭 링크 확인이 필요한지 여부를 결정하는 플래그입니다. 기본적으로 false 입니다 . 이는 지연 클래스 로딩이 사용됨을 의미합니다. 또한 설명서에 따르면 메서드의 기본 구현에서는 findLoadedClass(String name)클래스가 이전에 이미 로드되었는지 확인하고 로드된 경우 이 클래스에 대한 참조를 반환하는 호출이 이루어집니다. 그렇지 않으면 상위 로더의 클래스 로딩 메소드가 호출됩니다. 로더 중 어느 것도 로드된 클래스를 찾을 수 없으면 각각은 역순으로 해당 클래스를 찾아서 로드하려고 시도하며 findClass(String name). 이에 대해서는 "클래스 로딩 방식" 장에서 더 자세히 논의할 것입니다. 그리고 마지막으로 클래스가 로드된 후 확인 플래그에 따라 심볼릭 링크를 통해 클래스를 로드할지 여부가 결정됩니다. 명확한 예는 클래스 로딩 단계에서 해결 단계를 호출할 수 있다는 것입니다. 따라서 클래스를 확장 ClassLoader하고 해당 메서드를 재정의함으로써 커스텀 로더는 바이트코드를 가상 머신에 전달하기 위한 자체 로직을 구현할 수 있습니다. Java는 "현재" 클래스 로더 개념도 지원합니다. 현재 로더는 현재 실행 중인 클래스를 로드한 것입니다. 각 클래스는 어떤 로더가 로드되었는지 알고 있으며 해당 클래스를 호출하여 이 정보를 얻을 수 있습니다 String.class.getClassLoader(). 모든 애플리케이션 클래스의 경우 "현재" 로더는 일반적으로 시스템 로더입니다.

클래스 로딩의 세 가지 원칙

  • 대표단

    클래스 로드 요청은 상위 로더로 전달되며, 상위 로더가 클래스를 찾아서 로드할 수 없는 경우에만 클래스 자체를 로드하려는 시도가 이루어집니다. 이 접근 방식을 사용하면 기본 클래스에 최대한 가까운 로더를 사용하여 클래스를 로드할 수 있습니다. 이를 통해 수업 가시성이 극대화됩니다. 각 로더는 로드된 클래스의 기록을 캐시에 저장합니다. 이러한 클래스 집합을 범위라고 합니다.

  • 시계

    로더는 "자신의" 클래스와 "부모"의 클래스만 볼 수 있으며 "자식"이 로드한 클래스에 대해서는 전혀 알 수 없습니다.

  • 독창성

    클래스는 한 번만 로드할 수 있습니다. 위임 메커니즘은 클래스 로딩을 시작하는 로더가 이전에 JVM에 로드된 클래스를 오버로드하지 않도록 합니다.

따라서 부트로더를 작성할 때 개발자는 이 세 가지 원칙을 따라야 합니다.

클래스 로딩 방식

클래스 로드 호출이 발생하면 현재 로더의 이미 로드된 클래스 캐시에서 이 클래스를 검색합니다. 원하는 클래스가 이전에 로드되지 않은 경우 위임 원칙에 따라 계층 구조에서 한 수준 더 높은 상위 로더에 제어가 전달됩니다. 상위 로더는 또한 캐시에서 원하는 클래스를 찾으려고 시도합니다. 클래스가 이미 로드되었고 로더가 해당 위치를 알고 있는 경우 Class해당 클래스의 객체가 반환됩니다. 그렇지 않은 경우 기본 부트로더에 도달할 때까지 검색이 계속됩니다. 기본 로더에 필요한 클래스에 대한 정보가 없는 경우(즉, 아직 로드되지 않은 경우) 해당 로더가 알고 있는 클래스 위치에서 이 클래스의 바이트코드를 검색합니다. 로드되면 제어는 자식 로더로 다시 돌아가고 자식 로더는 알려진 소스에서 로드를 시도합니다. 위에서 언급했듯이 기본 로더의 클래스 위치는 rt.jar 라이브러리이고, 확장 로더의 경우 - jre/lib/ext 확장이 있는 디렉토리, 시스템의 경우 - CLASSPATH, 사용자의 경우 다른 것일 수 있습니다. . 따라서 클래스 로딩의 진행은 루트 로더에서 현재 클래스로 반대 방향으로 진행됩니다. 클래스의 바이트코드가 발견되면 클래스가 JVM에 로드되고 해당 유형의 인스턴스가 획득됩니다 Class. 쉽게 볼 수 있듯이 설명된 로딩 방식은 위의 메서드 구현과 유사합니다 loadClass(String name). 아래 다이어그램에서 이 다이어그램을 볼 수 있습니다.
JVM에 클래스가 로드되는 방법 - 2

결론적으로

언어 학습의 첫 번째 단계에서 Java에서 클래스가 로드되는 방식을 특별히 이해할 필요는 없지만 이러한 기본 원칙을 알면 또는 와 같은 오류가 발생할 때 절망을 피하는 데 도움이 ClassNotFoundException됩니다 NoClassDefFoundError. 글쎄, 아니면 적어도 문제의 근본 원인이 무엇인지 대략적으로 이해하십시오. ClassNotFoundException따라서 프로그램 실행 중에 클래스가 동적으로 로드될 때, 즉 로더가 캐시나 클래스 경로에서 필요한 클래스를 찾을 수 없는 경우 예외가 발생합니다. 그러나 이 오류 NoClassDefFoundError는 더 중요하며 컴파일 중에 필요한 클래스를 사용할 수 있었지만 프로그램 실행 중에는 표시되지 않았을 때 발생합니다. 프로그램이 사용하는 라이브러리를 포함하는 것을 잊어버린 경우 이런 일이 발생할 수 있습니다. 글쎄, 작업에 사용하는 도구 구조의 원리를 이해한다는 사실(깊이에 대한 명확하고 상세한 몰입이 반드시 필요한 것은 아님)은 이 메커니즘 내부에서 발생하는 프로세스에 대한 이해에 어느 정도 명확성을 추가합니다. 회전하면 이 도구를 자신있게 사용할 수 있습니다.

출처

ClassLoader가 Java에서 작동하는 방식 전반적으로 접근 가능한 정보 표시가 포함된 매우 유용한 소스입니다. 클래스 로드, ClassLoader 상당히 긴 기사이지만 동일한 기사를 사용하여 자신만의 로더 구현을 만드는 방법에 중점을 둡니다. ClassLoader: 클래스의 동적 로딩 안타깝게도 현재는 이 리소스를 사용할 수 없지만 거기에서 클래스 로딩 체계가 포함된 가장 이해하기 쉬운 다이어그램을 찾았으므로 추가하지 않을 수 없습니다. Java SE 사양: 5장. 로드, 링크 및 초기화
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION