>Java >java지도 시간 >Java 학습을 위한 JVM 클래스 로딩 메커니즘

Java 학습을 위한 JVM 클래스 로딩 메커니즘

青灯夜游
青灯夜游앞으로
2018-10-16 16:35:252034검색

이 기사에서는 Java 학습에서 Jvm 클래스의 로딩 메커니즘을 소개합니다. 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.

1. 개요

가상 머신은 클래스 파일(바이너리 바이트 스트림)을 메모리에 로드하고 데이터를 검증, 변환, 구문 분석 및 초기화하여 최종적으로 가상 머신에서 직접 사용할 수 있는 Java 유형을 형성합니다. 일련의 프로세스는 클래스 로딩 메커니즘입니다.

2. 클래스 로딩 타이밍

클래스는 가상 머신에 의해 메모리에 로드된 후 메모리에서 언로드될 때까지 시작됩니다. 전체 수명 주기에는 다음이 포함됩니다. 로드 - 확인 - 준비 - 구문 분석 - 초기화 - 사용 ——제거 이 7단계입니다. 검증, 준비, 구문 분석의 세 부분을 총칭하여 연결이라고 합니다.

라이프사이클 다이어그램은 다음과 같습니다.

로딩, 검증, 준비, 초기화, 언로딩의 5단계 순서가 정해져 있는데, 클래스의 로딩 과정은 이 순서대로 시작해야 하는데 파싱이 진행됩니다. 스테이지는 반드시 그런 것은 아닙니다. 특정 상황에서 초기화 후에 시작될 수 있으며 이는 Java 언어의 동적 바인딩도 지원하기 위한 것입니다.

클래스의 초기화 단계를 촉발할 수 있는 상황은 무엇인가요? (전제: 로딩, 검증, 준비가 자연스럽게 실행되었습니다.)

  1. new, getstatic, putstatic, Invokestatic 4가지 명령어를 만나면 클래스가 초기화되지 않으면 초기화가 실행됩니다. (이 4가지 명령어는 가장 일반적인 시나리오: 새로운 객체 인스턴스화, 클래스의 정적 필드 읽기 또는 설정(최종 수정 또는 상수 풀에 추가된 정적 필드 제외), 클래스의 정적 메서드 호출

  2. 리플렉션을 사용할 때

  3. 클래스를 초기화할 때 상위 클래스가 초기화되지 않은 경우 먼저 상위 클래스의 초기화를 트리거해야 합니다.

  4. 가상 머신이 시작되면 메인 클래스를 지정해야 합니다. (메인 메소드를 포함하는 클래스), virtual 이 클래스를 먼저 초기화하는 것이 기회입니다

  5. jdk1.7 동적 언어 지원을 사용할 때 java.lang.invoke.MethodHandle 인스턴스의 최종 구문 분석 결과가 REF_getStatic, REF_putStatic, REF_invokeStatic의 메소드 핸들입니다. 이 핸들에 해당하는 클래스가 초기화되지 않은 경우 먼저 초기화를 트리거해야 합니다

참고: 클래스를 참조하는 모든 메서드는 초기화(수동 참조)를 트리거하지 않습니다. 예: 배열 생성, final-참조 수정된 변수 및 상위 클래스를 참조하는 하위 클래스 클래스의 정적 변수는 하위 클래스의 초기화를 트리거하지 않지만 상위 클래스의 초기화를 트리거합니다

3. 클래스 로딩 프로세스

- 로딩

로딩은 클래스의 한 단계입니다. 로딩 단계에서 가상 머신은 다음 세 가지를 완료해야 합니다

  1. 클래스의 정규화된 이름을 통해 이 클래스를 정의하는 바이너리 바이트 스트림을 가져옵니다

  2. 이로 표시되는 정적 저장 구조를 변환합니다. 바이트 스트림을 메소드 영역의 런타임 데이터 구조

  3. 메모리 내에서 이 클래스를 나타내는 java.lang.Object 객체를 메소드 영역

에서 이 클래스의 다양한 데이터에 대한 액세스 지점으로 생성합니다. 다른 단계와 비교 클래스 로딩 중 로딩 단계(정확하게 말하면 클래스는 로딩 단계에서 획득됩니다. 이진 바이트 스트림의 작업)는 개발자가 가장 쉽게 제어할 수 있습니다. 로딩 단계는 시스템에서 제공하는 부트 클래스 로더를 사용하여 완료할 수도 있고, 개발자의 사용자 정의 클래스 로더(즉, 클래스 로더의 loadClass() 메서드 재정의)로 완료할 수도 있기 때문입니다.

로딩이 완료된 후 외부 바이너리 바이트 스트림을 가상 머신이 요구하는 형식으로 변환하여 메소드 영역에 저장한 후 java.lang.Class 클래스의 객체가 메모리에 인스턴스화됩니다. 이 개체는 프로그램이 메서드 영역에서 이러한 유형의 데이터에 액세스하기 위한 외부 인터페이스 역할을 합니다.

일부 로딩 단계와 연결 단계가 얽혀 있어 로딩이 완료될 때까지 검증 및 기타 작업을 수행할 수 없습니다. 로딩에 끼어 있는 이러한 동작은 여전히 ​​연결 단계에 속하며, 이 두 단계의 시작 시간은 여전히 ​​고정된 순서를 유지합니다.

- 확인

확인은 로드된 바이너리 바이트 스트림에 포함된 정보가 가상 머신 사양을 준수하는지 확인하기 위한 연결의 첫 번째 단계입니다.

검증 단계는 대략 다음 4가지 검증 작업으로 나뉩니다.

파일 형식 검증: 바이트 스트림이 클래스 파일 형식 사양을 준수하는지 확인합니다. 예: 매직 넘버 0xCAFEBABE로 시작하는지, 메이저 및 마이너 버전 번호가 현재 가상 머신의 처리 범위 내에 있는지, 상수 풀의 상수에 지원되지 않는 유형이 있는지...

메타데이터 검증: 바이트코드로 설명된 정보의 의미론적 분석. 예: 이 클래스에 상위 클래스가 있는지, 해당 클래스가 상위 클래스를 올바르게 상속하는지 여부.

바이트코드 검증: 데이터 흐름과 제어 흐름 분석을 통해 프로그램 의미론이 적법하고 논리적이라고 판단합니다. 런타임 중에 가상 머신에 해를 끼치지 않음).

심볼 참조 검증: 파싱 작업이 정상적으로 실행될 수 있는지 확인하세요.

검증 단계는 매우 중요하지만 반드시 필요한 단계는 아닙니다(프로그램 실행 시간에 영향을 주지 않기 때문에). 실행 중인 모든 코드가 반복적으로 사용되고 검증된 경우 -Xverify:none 매개변수를 사용하여 구현 단계에서 검증을 끌 수 있습니다.

- 클래스 변수에 대한 메모리를 공식적으로 할당하고 클래스 변수의 초기 값을 설정하도록 준비

하세요. 이러한 변수가 사용하는 메모리는 메소드 영역에 할당됩니다.

참고:

  • 이번에는 인스턴스 변수가 아닌 정적 변수만 할당됩니다. 인스턴스 변수는 객체 인스턴스와 함께 Java 힙에 할당됩니다.

  • 초기 값은 일반적으로 다음의 데이터 유형입니다. 0 값. 정적 변수 public static int value = 123을 정의하는 경우 value의 초기 값은 준비 단계에서 123이 아닌 0입니다.

  • final로 수정된 변수는 준비 단계에서 속성에 지정된 값으로 초기화됩니다. 예: public static final int 값 = 123이면 준비 단계의 초기 값은 123입니다.

- 구문 분석

구문 분석 단계는 가상 머신이 상수 풀의 기호 참조를 직접 참조로 바꾸는 프로세스입니다. 구문 분석 작업은 주로 클래스 또는 인터페이스, 필드, 클래스 메서드, 인터페이스 메서드, 메서드 유형, 메서드 핸들 및 호출 사이트 한정자 등 7가지 유형의 기호 참조에 대해 수행됩니다.

기호 참조: 기호 집합을 사용하여 참조 대상을 설명합니다. 기호는 모든 형태의 리터럴이 될 수 있습니다.

직접 참조: 대상에 대한 포인터, 상대 오프셋 또는 대상을 간접적으로 찾을 수 있는 핸들.

-초기화

초기화 단계는 클래스 생성자() 메서드를 실행하는 프로세스입니다. 준비 단계에서는 변수에 시스템이 요구하는 초기값을 할당하고, 초기화 단계에서는 프로그래머가 설정한 매개변수 값에 따라 클래스 변수 및 기타 자원을 초기화한다.

클래스 생성자 () 메서드: 컴파일러가 클래스에 있는 모든 클래스 변수의 할당 동작을 자동으로 수집하고 정적 코드 블록의 명령문을 병합하여 생성됩니다.

컴파일러가 수집하는 순서는 소스 파일에 명령문이 나타나는 순서에 따라 결정됩니다. 정적 코드 블록은 정적 블록 이전에 정의된 변수에만 액세스할 수 있으며 이후에 정의된 변수에는 이전 정적 블록이지만 액세스할 수 없습니다.

非法向前引用示例

public class SuperClass {
    public static int va;
    static {
        value = 1;            //可以编译通过
        va = value;           //报错  非法向前引用
        System.out.println("父类初始化");
    }

    public static int value = 123;
}

() 메서드는 클래스나 인터페이스에 필요하지 않습니다. 클래스에 정적 코드 블록이 없고 변수에 대한 할당 작업이 없으면 컴파일러는 이 클래스에 대해 < .clinit> 메소드

정적 블록은 인터페이스에서 사용할 수 없지만 변수 할당 작업은 계속 수행할 수 있으므로 인터페이스와 클래스는 메소드를 생성합니다. 차이점은 인터페이스 초기화에는 상위 클래스의 초기화가 먼저 필요하지 않다는 것입니다. 상위 인터페이스의 초기화는 상위 인터페이스의 변수가 사용될 때만 트리거됩니다. 또한 인터페이스의 구현 클래스는 인터페이스 인스턴스화를 트리거하지 않습니다.

가상 머신은 클래스의 () 메서드가 여러 스레드에서 올바르게 잠기고 동기화되도록 보장합니다. 여러 스레드가 클래스를 초기화하는 경우 하나의 스레드만 () 메소드에 장기 실행 작업이 있는 경우 여러 스레드가 차단될 수 있습니다. 실제 애플리케이션에서는 이러한 차단이 매우 숨겨져 있는 경우가 많습니다.

4. 클래스 로더

가상 머신 설계 팀은 Java 가상 머신 외부의 클래스 로딩에 "클래스의 정규화된 이름을 통해 이 클래스를 설명하는 바이너리 바이트 스트림을 얻는" 작업을 애플리케이션에서 결정합니다. 필요한 수업을 받습니다. 이 작업을 구현하는 코드 블록을 클래스 로더라고 합니다.

Java 개발자의 관점에서 클래스 로더는 대략 다음 세 가지 유형으로 나뉩니다.

Bootstrap Classloader(Bootstrap Classloader): lib에 파일을 저장하는 역할을 담당합니다(Javahome은 jdk 설치 디렉터리입니다). 또는 -Xbootclasspath 매개변수로 지정된 경로에 있고 가상 머신에서 인식됩니다(rt.jar와 같은 파일 이름으로만 인식되며 이름이 일치하지 않는 클래스 라이브러리는 아래에 배치되더라도 로드되지 않습니다). lib) 클래스 라이브러리가 가상 머신 메모리에 로드됩니다. 시작 클래스 로더는 Java 프로그램에서 직접 사용할 수 없습니다.

Extension Classloader: 이 로더는 libext 디렉토리 로드를 담당하는 sun.misc.Launcher$ExtClassLoader에 의해 구현되거나 java.ext.dirs 시스템 변수에 의해 지정됩니다. 시스템의 모든 클래스 라이브러리 길. 개발자는 확장 클래스 로더를 직접 사용할 수 있습니다.

Application Classloader: 이 로더는 사용자 클래스 경로(ClassPath)에 지정된 클래스 라이브러리를 로드하는 sun.misc.Launcher$AppClassLoader에 의해 구현됩니다. 개발자는 이 로더를 직접 사용할 수 있습니다. 애플리케이션에 사용자 정의 클래스 로더가 없는 경우 이는 기본적으로 프로그램에서 실행되는 클래스 로더입니다. (시스템 로더)

我们的应用程序都是由这3种类加载器相互配合进行加载的。如果有必要,还可以加入自定义的类加载器。

这些类加载器之间的关系如下图:

 

5.双亲委派模型: 

双亲委派模型的工作过程是:如果一个类加载器收到了一个类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层的加载器都是如此,因此所有的加载请求最终都应该到达顶层的启动类加载器。只有当父加载无法完成这个加载请求时,子加载器才会尝试自己去加载。

双亲委派机制:

1、当ApplicationClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。

3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;

4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

ClassLoader源码分析:    

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 先检查此类是否已被加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //委派给父类加载器去加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        //如果没有父加载器,则调用启动类加载器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                //如果父加载器无法加载,则调用本身加载器去加载
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }                                         

双亲委派模型意义:

  • 系统类防止内存中出现多份同样的字节码

  • 保证Java程序安全稳定运行

参考

《深入理解Java虚拟机》 

总结:以上就是本篇文的全部内容,希望能对大家的学习有所帮助。更多相关教程请访问Java视频教程java开发图文教程bootstrap视频教程

위 내용은 Java 학습을 위한 JVM 클래스 로딩 메커니즘의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 cnblogs.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제