>  기사  >  Java  >  Java 학습: 클래스 로딩 메커니즘의 상세 분석

Java 학습: 클래스 로딩 메커니즘의 상세 분석

php是最好的语言
php是最好的语言원래의
2018-08-06 11:30:191493검색

클래스 로딩 메커니즘에 대한 심층적인 이해

지침:

본문을 시작하기 전에 이 글을 쓰는 목적과 이점에 대해 이야기하고 싶습니다. 배운 내용을 바탕으로 뭔가를 할 수 있다. 정리하자면, 뭔가를 배우고 생각을 정리하는 데는 시간이 오래 걸릴 때도 있지만, 며칠 지나도 생각해보면 여전히 흐려질 것 같아서 시간을 들여야 한다. 관련 정보를 찾고 정보를 살펴봅니다. 이제 이러한 요약을 작성하면 새로 배운 지식을 통합하고 심화할 수 있을 뿐만 아니라 미래를 되돌아볼 때 집중된 정보와 일반적인 아이디어를 얻을 수 있어 과거의 기억을 빠르게 복원할 수 있습니다. 손으로 직접 기록해두거나, 읽으면서 기록해 두는데, 뒤돌아보면 찾기도 힘들고, 잃어버리기 쉽기 때문에 블로그에 직접 글을 씁니다.

개요

이 기사는 제가 jvm 클래스 로딩 메커니즘을 배울 때 인터넷에 있는 일부 정보를 수집하고 요약한 것입니다. 구체적인 참조 주소는 나중에 제공됩니다. 여기에는 일반적인 프로세스를 요약하고 많은 개념적 세부 사항에 대한 설명을 풍부하게 하는 많은 정보가 참조되어 있습니다.

대략 JVM类加载机制我准备分两篇文章来分别介绍,一片主要介绍jvm中类的生命周期,另一篇着重讲一下类加载器。单独讲解类加载器是因为 类加载这部分是唯一我们可以通过自己的代码程序进行干预的部分,而其他部分都是jvm内部直接完成的。介绍完类加载接下来会有一篇文章讲解反射, 상관관계가 많기 때문에 기회가 된다면 바이트코드에 대해서도 이야기해보고 싶습니다.

본문을 시작하기 전에 먼저 두 장의 그림을 살펴보겠습니다
먼저 Java 프로그램의 실행 흐름도를 살펴보세요

그 다음 jvm의 일반적인 물리적 구조 다이어그램을 살펴보세요

이 기사에서는 이 두 그림만 다룰 것입니다. 사진의 일부일 뿐 전부는 아닙니다. 이 두 사진에 대한 전반적인 인상이 필요합니다.

클래스 로딩 메커니즘의 개념

* 자바 가상 머신은 클래스를 기술하는 데이터를 Class 파일에서 메모리로 로딩하고, 데이터를 검증, 변환, 파싱, 초기화하여 최종적으로 직접적으로 사용할 수 있는 자바 타입을 형성한다. 가상 머신에서 사용됩니다. 이는 가상 머신의 로딩 메커니즘입니다. *

클래스 로더가 클래스 파일을 로드한 후 클래스 구조를 설명하는 메타 정보 객체가 JVM에 형성됩니다. 이 메타 정보 객체를 통해 클래스의 구조 정보를 얻을 수 있습니다. 생성자, 속성, 메소드 등 Java 이 Class 관련 메타정보 객체를 통해 사용자가 Class 객체의 기능을 간접적으로 호출할 수 있도록 해줍니다. 여기에 우리가 자주 보는 Class 클래스가 있습니다.

클래스가 가상 머신 메모리에 로드되는 시점부터 메모리에서 언로드되는 시점까지 클래스의 전체 수명 주기에는 로딩, 확인, 준비, 확인, 초기화), 사용(사용) 및 언로드()가 포함됩니다. 하역) 7단계. 검증, 준비 및 구문 분석의 세 부분을 총칭하여 연결이라고 합니다. 이 7단계의 순서는 아래 그림에 나와 있습니다.

작업 메커니즘

클래스 로더는 클래스의 바이트코드 파일을 찾아 구성하는 것입니다. JVM이 내부적으로 표현하는 객체 구성 요소입니다. Java에서 클래스 로더는 클래스를 JVM에 로드하고 다음 단계를 수행합니다.

6 ㅋㅋㅋㅋㅋㅋㅋㅋㅋ Java 프로그램은 런타임 중에 동적 로딩 및 동적 링크를 통해 동적으로 확장될 수 있습니다. 예를 들어 인터페이스를 사용하는 애플리케이션을 작성하는 경우 실제 구현(다형성)을 지정하기 위해 런타임까지 기다릴 수 있으며 때로는 초기화 후에 구문 분석 프로세스가 실행될 수도 있습니다. 예: 동적 바인딩(다형성)
  • 표시된 대로 위 그림에서는 로딩, 검증, 준비, 초기화, 언로딩의 5단계 순서가 정해져 있습니다. 클래스의 로딩 프로세스는 이 순서대로 단계별로 시작되어야 하지만, 구문 분석 단계는 반드시 그렇지 않은 경우도 있습니다. , 초기화 단계 이후에 시작할 수 있습니다.

    클래스 라이프사이클의 각 단계는 일반적으로 하이브리드 방식으로 수행되며, 일반적으로 한 단계가 실행되는 동안 다른 단계를 호출하거나 활성화합니다.

  • 자세한 설명
  • 다른 분들의 정보를 참고해서 클래스 초기화 과정이 먼저 소개된 것을 발견했는데, 이로 인해 클래스 초기화가 무엇인지, 언제 이루어져야 하는지에 대한 오해가 생길 것 같습니다. 여기서는 클래스의 로딩 라이프사이클 순서대로 각 프로세스를 소개하겠습니다.

    1. 装载(加载)

    什么是类的装载

    类的装载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

    类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。

    加载.class文件的方式有:

    1. 从本地系统中直接加载2. 通过网络下载.class文件3. 从zip,jar等归档文件中加载.class文件4. 从专有数据库中提取.class文件5. 将Java源文件动态编译为.class文件
    • 1

    • 2

    • 3

    • 4

    • 5

    • 6

    在了解了什么是类的加载后,回头来再看jvm进行类加载阶段都做了什么。虚拟机需要完成以下三件事情:

    1.通过一个类的全限定名称来获取定义此类的二进制字节流。 
    
    2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 
    
    3.在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
    • 1

    • 2

    • 3

    • 4

    • 5

    • 6

    相对于类加载过程的其他阶段,加载阶段是开发期相对来说可控性比较强,该阶段既可以使用系统提供的类加载器完成,也可以由用户自定义的类加载器来完成,开发人员可以通过定义自己的类加载器去控制字节流的获取方式。关于这个过程的更多细节,我会在下一节细说,类的加载。
    加载阶段完成后,虚拟机外部的 二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。

    2. 验证

    验证的目的是为了确保Class文件中的字节流包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。不同的虚拟机对类验证的实现可能会有所不同,但大致都会完成以下四个阶段的验证:文件格式的验证、元数据的验证、字节码验证和符号引用验证。

     1)文件格式的验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理,该验证的主要目的是保证输入的字节流能正确地解析并存储于方法区之内。经过该阶段的验证后,字节流才会进入内存的方法区中进行存储,后面的三个验证都是基于方法区的存储结构进行的。 
    
     2)元数据验证:对类的元数据信息进行语义校验(其实就是对类中的各数据类型进行语法校验),保证不存在不符合Java语法规范的元数据信息。 
    
     3)字节码验证:该阶段验证的主要工作是进行数据流和控制流分析,对类的方法体进行校验分析,以保证被校验的类的方法在运行时不会做出危害虚拟机安全的行为。 
    
     4)符号引用验证:这是最后一个阶段的验证,它发生在虚拟机将符号引用转化为直接引用的时候(解析阶段中发生该转化,后面会有讲解),主要是对类自身以外的信息(常量池中的各种符号引用)进行匹配性的校验。
    • 1

    • 2

    • 3

    • 4

    • 5

    • 6

    • 7

    • 8

    3. 准备

    准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。  
    注:

    1)这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。 
    
    2)这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。
    • 1

    • 2

    • 3

    • 4

    4. 解析

    解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

    符号引用(Symbolic Reference):符号引用以一组符号来描述所引用的目标,符号引用可以是任何形式的字面量,符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经在内存中。 
    
    直接引用(Direct Reference) :直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般都不相同,如果有了直接引用,那引用的目标必定已经在内存中存在。 
    
    1、类或接口的解析:判断所要转化成的直接引用是对数组类型,还是普通的对象类型的引用,从而进行不同的解析。 
    2、字段解析:对字段进行解析时,会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段,如果有,则查找结束;如果没有,则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口,还没有,则按照继承关系从上往下递归搜索其父类,直至查找结束。 
    
    3、类方法解析:对类方法的解析与对字段解析的搜索步骤差不多,只是多了判断该方法所处的是类还是接口的步骤,而且对类方法的匹配搜索,是先搜索父类,再搜索接口。 
    
    4、接口方法解析:与类方法解析步骤类似,只是接口不会有父类,因此,只递归向上搜索父接口就行了。
    • 1

    • 2

    • 3

    • 4

    • 5

    • 6

    • 7

    • 8

    • 9

    • 10

    • 11

    5. 初始化

    类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了加载(Loading)阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。  
    初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:

    ①声明类变量时指定初始值
    
    ②使用静态代码块为类变量指定初始值
    • 1

    • 2

    • 3

    • 4

    JVM初始化步骤

    1、假如这个类还没有被加载和连接,则程序先加载并连接该类
    
    2、假如该类的直接父类还没有被初始化,则先初始化其直接父类
    
    3、假如类中有初始化语句,则系统依次执行这些初始化语句
    • 1

    • 2

    • 3

    • 4

    • 5

    • 6

    初始化阶段时执行类构造器()方法的过程。

    1)<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序所决定。 
    
    2)<clinit>()方法与类的构造函数不同,它不需要显式地调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕,因此在虚拟机中第一个执行的<clinit>()方法的类一定是java.lang.Object。 
    
    3)由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。 
    
    4)<clinit>()方法对于类或者接口来说并不是必需的,如果一个类中没有静态语句块也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>()方法。 
    5)接口中可能会有变量赋值操作,因此接口也会生成<clinit>()方法。但是接口与类不同,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法。只有当父接口中定义的变量被使用时,父接口才会被初始化。另外,接口的实现类在初始化时也不会执行接口的<clinit>()方法。 
    6)虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁和同步。如果有多个线程去同时初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其它线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。如果在一个类的<clinit>()方法中有耗时很长的操作,那么就可能造成多个进程阻塞。
    • 1

    • 2

    • 3

    • 4

    • 5

    • 6

    • 7

    • 8

    • 9

    • 10

    类初始化的触发条件:只有当对类的主动使用的时候才会导致类的初始化。

    (1)遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候,读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
    
    (2) 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
    
    (3) 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
    
    (4) 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
    • 1

    • 2

    • 3

    • 4

    • 5

    • 6

    • 7

    • 8

    只有上述四种情况会触发初始化,也称为对一个类进行主动引用,除此以外,所有其他方式都不会触发初始化,称为被动引用。
    关于上面的这四种说法,换一种通俗的解释应该对应下面的六种:

    (1) 创建类的实例,也就是new的方式
    
    (2) 访问某个类或接口的静态变量,或者对该静态变量赋值
    
    (3) 调用类的静态方法
    
    (4) 反射(如Class.forName(“com.shengsiyuan.Test”))
    
    (5) 初始化某个类的子类,则其父类也会被初始化
    
    (6) Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类
    • 1

    • 2

    • 3

    • 4

    • 5

    • 6

    • 7

    • 8

    • 9

    • 10

    • 11

    • 12

    结束生命周期

    在以下情况的时候,Java虚拟机会结束生命周期
       1. 执行了System.exit()方法
       2. 程序正常执行结束
       3. 程序在执行过程中遇到了异常或错误而异常终止
       4. 由于操作系统出现错误而导致Java虚拟机进程终止

    相关文章:

    Java虚拟机学习 - 类加载机制

    从JVM分析Java的类的加载和卸载机制

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

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.