Compréhension approfondie du mécanisme de chargement des classes
Instructions :
Avant de commencer le texte principal, j'aimerais parler du but et des avantages de la rédaction de ces articles. Le but principal est de faire une synthèse de ce que j'ai appris. Parfois, cela prend beaucoup de temps. pour apprendre quelque chose et le redresser. Certaines idées, mais elles deviendront floues quand j'y penserai dans quelques jours, je dois donc passer du temps à chercher des informations pertinentes et à parcourir les informations. Maintenant, en faisant un tel résumé, vous pouvez non seulement consolider et approfondir les connaissances nouvellement acquises, mais aussi avoir des informations concentrées et des idées générales lorsque vous regardez en arrière, ce qui peut rapidement restaurer votre mémoire dans le passé, je l'ai toujours fait ; je l'ai enregistré à la main. Je l'enregistre directement dans mon cahier ou en lisant, mais il est difficile de le retrouver quand je regarde en arrière et il est facile de le perdre. C'est pourquoi j'écris mon propre blog.
Cet article est une collection et un résumé de certaines informations sur Internet lorsque j'apprenais le mécanisme de chargement de classe jvm. Des adresses de référence spécifiques seront données plus tard. De nombreuses informations sont référencées ici, qui résument un processus général et enrichissent l'explication de nombreux détails conceptuels.
À propos de JVM类加载机制
Je vais le présenter dans deux articles, l'un présente principalement le cycle de vie des classes en jvm, et l'autre se concentre sur les chargeurs de classes. La raison pour laquelle j'explique le chargeur de classe séparément est que 类加载
cette partie est la seule partie sur laquelle nous pouvons intervenir via notre propre programme de code, tandis que d'autres parties sont complétées directement dans la jvm. Après avoir introduit le chargement des classes, il y aura un article expliquant 反射
, car ils ont beaucoup de corrélations. Si j'en ai l'occasion, j'aimerais aussi parler du bytecode.
Avant de commencer le texte, regardons d'abord deux images
Regardons d'abord l'organigramme d'exécution du programme Java
Et puis regardons le diagramme général de la structure physique de le jvm
Cet article ne couvrira qu'une partie de ces deux images, et ne conçoit pas l'intégralité du contenu. Vous devriez avoir une impression générale de ces deux images.
* La machine virtuelle Java charge les données décrivant la classe du fichier Classe dans la mémoire, et vérifie, convertit, analyse et initialise les données, et forme enfin un Le type Java utilisé directement par la machine virtuelle est le mécanisme de chargement de la machine virtuelle. *
Une fois le fichier de classe chargé par le chargeur de classe, un objet de méta-informations décrivant la structure de la classe sera formé dans la JVM. Grâce à cet objet de méta-informations, les informations structurelles de la classe. peuvent être obtenus : comme le constructeur, les propriétés et les méthodes, etc. Java permet aux utilisateurs d'appeler indirectement les fonctions de l'objet Class via cet objet de méta-information lié à la classe. Voici la classe Class que nous voyons souvent.
Depuis le moment où une classe est chargée dans la mémoire de la machine virtuelle jusqu'au moment où elle est déchargée de la mémoire, tout son cycle de vie comprend : le chargement, la vérification, la préparation et la résolution, l'initialisation (Initialisation), l'utilisation. (utilisation) et déchargement (déchargement) en sept étapes. Les trois parties de vérification, de préparation et d'analyse sont collectivement appelées liaison. La séquence de ces sept étapes est illustrée dans la figure ci-dessous :
Chargement de la classe. Le processeur doit trouver le fichier de bytecode de la classe et construire le composant objet représenté par la classe à l'intérieur de la JVM. En Java, le chargeur de classe charge une classe dans la JVM et suit les étapes suivantes :
(1) 装载:查找和导入Class文件; (2) 链接:把类的二进制数据合并到JRE中; (a)校验:检查载入Class文件数据的正确性; (b)准备:给类的静态变量分配存储空间; (c)解析:将符号引用转成直接引用; (3) 初始化:对类的静态变量,静态代码块执行初始化操作
1
2
3
4
5
6
7
8
9
10
11
12
Les programmes Java peuvent être étendus dynamiquement par le runtime Dynamic le chargement et la liaison dynamique sont implémentés ; par exemple : si vous écrivez une application qui utilise une interface, vous pouvez attendre l'exécution pour spécifier son implémentation réelle (polymorphisme). Le processus d'analyse peut parfois être exécuté après l'initialisation ; Polymorphisme)
Comme le montre la figure ci-dessus, l'ordre des cinq étapes de chargement, vérification, préparation, initialisation et déchargement est déterminé. Le processus de chargement de la classe doit commencer étape par étape dans cet ordre, et l'étape d'analyse Pas nécessairement, elle peut dans certains cas démarrer après la phase d'initialisation.
Chaque étape du cycle de vie d'une classe est généralement réalisée de manière hybride, appelant ou activant généralement une autre étape pendant l'exécution d'une étape.
Lorsque j'ai fait référence aux informations d'autres personnes, j'ai découvert que le processus d'initialisation de classe avait été introduit en premier, mais je pense que cela provoquera un malentendu sur ce qu'est l'initialisation de classe, et le le moment choisi devrait arriver. Ici, je présenterai chaque processus dans l'ordre du cycle de vie de chargement de la classe.
类的装载指的是将类的.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类的对象,这样便可以通过该对象访问方法区中的这些数据。
验证的目的是为了确保Class文件中的字节流包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。不同的虚拟机对类验证的实现可能会有所不同,但大致都会完成以下四个阶段的验证:文件格式的验证、元数据的验证、字节码验证和符号引用验证。
1)文件格式的验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理,该验证的主要目的是保证输入的字节流能正确地解析并存储于方法区之内。经过该阶段的验证后,字节流才会进入内存的方法区中进行存储,后面的三个验证都是基于方法区的存储结构进行的。 2)元数据验证:对类的元数据信息进行语义校验(其实就是对类中的各数据类型进行语法校验),保证不存在不符合Java语法规范的元数据信息。 3)字节码验证:该阶段验证的主要工作是进行数据流和控制流分析,对类的方法体进行校验分析,以保证被校验的类的方法在运行时不会做出危害虚拟机安全的行为。 4)符号引用验证:这是最后一个阶段的验证,它发生在虚拟机将符号引用转化为直接引用的时候(解析阶段中发生该转化,后面会有讲解),主要是对类自身以外的信息(常量池中的各种符号引用)进行匹配性的校验。
1
2
3
4
5
6
7
8
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。
注:
1)这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。 2)这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。
1
2
3
4
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用(Symbolic Reference):符号引用以一组符号来描述所引用的目标,符号引用可以是任何形式的字面量,符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经在内存中。 直接引用(Direct Reference) :直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般都不相同,如果有了直接引用,那引用的目标必定已经在内存中存在。 1、类或接口的解析:判断所要转化成的直接引用是对数组类型,还是普通的对象类型的引用,从而进行不同的解析。 2、字段解析:对字段进行解析时,会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段,如果有,则查找结束;如果没有,则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口,还没有,则按照继承关系从上往下递归搜索其父类,直至查找结束。 3、类方法解析:对类方法的解析与对字段解析的搜索步骤差不多,只是多了判断该方法所处的是类还是接口的步骤,而且对类方法的匹配搜索,是先搜索父类,再搜索接口。 4、接口方法解析:与类方法解析步骤类似,只是接口不会有父类,因此,只递归向上搜索父接口就行了。
1
2
3
4
5
6
7
8
9
10
11
类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了加载(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虚拟机进程终止
相关文章:
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!