Maison  >  Article  >  Java  >  Introduction détaillée à l'architecture de la machine virtuelle Java

Introduction détaillée à l'architecture de la machine virtuelle Java

零下一度
零下一度original
2017-06-25 13:33:571190parcourir

Cycle de vie de la machine virtuelle JAVA

La tâche impérative d'une instance de machine virtuelle Java d'exécution est la suivante : responsable de l'exécution d'un programme Java. Lorsqu'un programme Java est démarré, une instance de machine virtuelle naît. Lorsque le programme est fermé et quitté, l'instance de machine virtuelle mourra également. Si trois programmes Java sont exécutés simultanément sur le même ordinateur, trois instances de machine virtuelle Java seront obtenues. Chaque programme Java s'exécute dans sa propre instance de machine virtuelle Java.

Une instance de machine virtuelle Java exécute un programme Java en appelant la méthode main() d'une classe initiale. La méthode main() doit être publique, statique, renvoyer void et accepter un tableau de chaînes comme paramètre. Toute classe avec une telle méthode main() peut être utilisée comme point de départ pour exécuter un programme Java.

public class Test {public static void main(String[] args) {// TODO Auto-generated method stub
        System.out.println("Hello World");
    }

}

Dans l'exemple ci-dessus, la méthode main() dans la classe initiale du programme Java sera le point de départ du thread initial du programme, et tous les autres threads sont démarrés par ce thread initial .

Il existe deux types de threads à l'intérieur de la machine virtuelle Java : les threads démons et les threads non démons. Les threads démons sont généralement utilisés par la machine virtuelle elle-même, tels que les threads qui effectuent des tâches de garbage collection. Cependant, un programme Java peut également marquer n'importe quel thread qu'il crée comme thread démon. Le thread initial du programme Java, celui qui démarre dans main(), est un thread non-démon.

Tant qu'il y aura des threads non-démons en cours d'exécution, le programme Java continuera à s'exécuter. Lorsque tous les threads non démons du programme se terminent, l'instance de machine virtuelle se ferme automatiquement. Si le responsable de la sécurité le permet, le programme lui-même peut également se terminer en appelant la méthode exit() de la classe Runtime ou de la classe System.

Architecture de la machine virtuelle JAVA

La figure suivante est le diagramme de structure de la machine virtuelle JAVA. Chaque machine virtuelle Java possède un sous-système de chargement de classe, basé sur le nom complet donné Load. type (classe ou interface). De même, chaque machine virtuelle Java dispose d'un moteur d'exécution, qui se charge d'exécuter les instructions contenues dans les méthodes de la classe chargée.

 

Lorsque la machine virtuelle JAVA exécute un programme, elle a besoin de mémoire pour stocker de nombreuses choses, telles que : le bytecode, d'autres informations obtenues à partir des fichiers de classe chargés, les objets créés par le programme , paramètres passés aux méthodes, valeurs de retour, variables locales, etc. La machine virtuelle Java organise ces éléments en plusieurs « zones de données d'exécution » pour une gestion facile.

Certaines zones de données d'exécution sont partagées par tous les threads du programme, tandis que d'autres ne peuvent appartenir qu'à un seul thread. Chaque instance de machine virtuelle Java possède une zone de méthode et un tas, qui sont partagés par tous les threads de l'instance de machine virtuelle. Lorsqu'une machine virtuelle charge un fichier de classe, elle analyse les informations de type à partir des données binaires contenues dans le fichier de classe. Ensuite, placez ces informations de type dans la zone méthode. Lorsque le programme est en cours d'exécution, la machine virtuelle place tous les objets créés par le programme pendant son exécution dans le tas.

 

Lorsque chaque nouveau thread est créé, il obtiendra son propre registre PC (compteur de programme) et une pile Java si le thread exécute une méthode Java (non native. méthode), alors la valeur du registre PC pointera toujours vers la prochaine instruction à exécuter, et sa pile Java stockera toujours l'état de l'appel de méthode Java dans le thread - y compris ses variables locales, qui sont les paramètres passés dans lors de l'appel, la valeur de retour et les résultats intermédiaires de l'opération, etc. L'état des appels de méthode locale est stocké dans la pile de méthodes locales dans une méthode qui dépend de l'implémentation spécifique, ou peut être dans un registre ou dans une autre zone mémoire liée à une implémentation spécifique.

La pile Java est composée de plusieurs cadres de pile. Un cadre de pile contient l'état d'un appel de méthode Java. Lorsqu'un thread appelle une méthode Java, la machine virtuelle pousse un nouveau cadre de pile dans la pile Java du thread. Lorsque la méthode est renvoyée, le cadre de pile est extrait de la pile Java et supprimé.

La machine virtuelle Java n'a pas de registres et son jeu d'instructions utilise la pile Java pour stocker les données intermédiaires. La raison de cette conception est de garder le jeu d'instructions de la machine virtuelle Java aussi compact que possible et de faciliter la mise en œuvre de la machine virtuelle Java sur des plates-formes comportant peu de registres à usage général. De plus, l'architecture basée sur la pile de la machine virtuelle Java contribue également à l'optimisation du code des compilateurs dynamiques et des compilateurs juste à temps implémentés par certaines machines virtuelles pendant l'exécution.

La figure suivante représente la zone mémoire créée par la machine virtuelle Java pour chaque thread. Ces zones mémoire sont privées et aucun thread ne peut accéder au registre PC ou à la pile Java d'un autre thread.

 

La figure ci-dessus montre un instantané d'une instance de machine virtuelle avec trois threads en cours d'exécution. Les threads 1 et 2 exécutent des méthodes Java, tandis que le thread 3 exécute une méthode native.

  Java栈都是向下生长的,而栈顶都显示在图的底部。当前正在执行的方法的栈帧则以浅色表示,对于一个正在运行Java方法的线程而言,它的PC寄存器总是指向下一条将被执行的指令。比如线程1和线程2都是以浅色显示的,由于线程3当前正在执行一个本地方法,因此,它的PC寄存器——以深色显示的那个,其值是不确定的。

 数据类型

  Java虚拟机是通过某些数据类型来执行计算的,数据类型可以分为两种:基本类型和引用类型,基本类型的变量持有原始值,而引用类型的变量持有引用值。

  

  Java语言中的所有基本类型同样也都是Java虚拟机中的基本类型。但是boolean有点特别,虽然Java虚拟机也把boolean看做基本类型,但是指令集对boolean只有很有限的支持,当编译器把Java源代码编译为字节码时,它会用int或者byte来表示boolean。在Java虚拟机中,false是由整数零来表示的,所有非零整数都表示true,涉及boolean值的操作则会使用int。另外,boolean数组是当做byte数组来访问的,但是在“堆”区,它也可以被表示为位域。

  Java虚拟机还有一个只在内部使用的基本类型:returnAddress,Java程序员不能使用这个类型,这个基本类型被用来实现Java程序中的finally子句。该类型是jsr, ret以及jsr_w指令需要使用到的,它的值是JVM指令的操作码的指针。returnAddress类型不是简单意义上的数值,不属于任何一种基本类型,并且它的值是不能被运行中的程序所修改的。

  Java虚拟机的引用类型被统称为“引用(reference)”,有三种引用类型:类类型、接口类型、以及数组类型,它们的值都是对动态创建对象的引用。类类型的值是对类实例的引用;数组类型的值是对数组对象的引用,在Java虚拟机中,数组是个真正的对象;而接口类型的值,则是对实现了该接口的某个类实例的引用。还有一种特殊的引用值是null,它表示该引用变量没有引用任何对象。

  JAVA中方法参数的引用传递

  java中参数的传递有两种,分别是按值传递和按引用传递。按值传递不必多说,下面就说一下按引用传递。

  “当一个对象被当作参数传递到一个方法”,这就是所谓的按引用传递。

public class User {    private String name;public String getName() {return name;
    }public void setName(String name) {this.name = name;
    }
    
}
public class Test {    public void set(User user){
        user.setName("hello world");
    }    public static void main(String[] args) {
        
        Test test = new Test();
        User user = new User();
        test.set(user);
        System.out.println(user.getName());
    }
}

  上面代码的输出结果是“hello world”,这不必多说,那如果将set方法改为如下,结果会是多少呢?

public void set(User user){
        user.setName("hello world");
        user = new User();
        user.setName("change");
    }

  答案依然是“hello world”,下面就让我们来分析一下如上代码。

  首先

User user = new User();

  是在堆中创建了一个对象,并在栈中创建了一个引用,此引用指向该对象,如下图:

 

test.set(user);

  是将引用user作为参数传递到set方法,注意:这里传递的并不是引用本身,而是一个引用的拷贝。也就是说这时有两个引用(引用和引用的拷贝)同时指向堆中的对象,如下图:

 

user.setName("hello world");

  在set()方法中,“user引用的拷贝”操作堆中的User对象,给name属性设置字符串"hello world"。如下图:

  

 

user = new User();

  在set()方法中,又创建了一个User对象,并将“user引用的拷贝”指向这个在堆中新创建的对象,如下图:

  

user.setName("change");

  在set()方法中,“user引用的拷贝”操作的是堆中新创建的User对象。

 

  set()方法执行完毕,目光再回到mian()方法

System.out.println(user.getName());

  因为之前,"user引用的拷贝"已经将堆中的User对象的name属性设置为了"hello world",所以当main()方法中的user调用getName()时,打印的结果就是"hello world"。如下图:

  

Sous-système de chargement de classe

Dans la machine virtuelle JAVA, la partie responsable de la recherche et du chargement des types est appelée le sous-système de chargement de classe.

La machine virtuelle JAVA dispose de deux chargeurs de classes : un chargeur de classes de démarrage et un chargeur de classes défini par l'utilisateur. Le premier fait partie de l'implémentation de la machine virtuelle JAVA et le second fait partie du programme Java. Les classes chargées par différents chargeurs de classes seront placées dans différents espaces de noms à l'intérieur de la machine virtuelle.

Le sous-système chargeur de classes implique plusieurs autres composants de la machine virtuelle Java, ainsi que plusieurs classes de la bibliothèque java.lang. Par exemple, un chargeur de classe défini par l'utilisateur est un objet Java ordinaire et sa classe doit être dérivée de la classe java.lang.ClassLoader. Les méthodes définies dans ClassLoader fournissent une interface permettant aux programmes d'accéder au mécanisme de chargement de classe. De plus, pour chaque type chargé, la machine virtuelle JAVA créera une instance de la classe java.lang.Class pour représenter le type. Comme tous les autres objets, les chargeurs de classe définis par l'utilisateur et les instances de la classe Class sont placés dans la zone de tas en mémoire et les informations de type chargées se trouvent dans la zone de méthode.

En plus de localiser et d'importer des fichiers de classe binaires, le sous-système du chargeur de classe doit également être chargé de vérifier l'exactitude de la classe importée, d'allouer et d'initialiser la mémoire pour les variables de classe et d'aider à résoudre les références de symboles. Ces actions doivent être effectuées strictement dans l'ordre suivant :

(1) Chargement - Rechercher et charger des données binaires de type.

(2) Connexion - points de vérification, de préparation et d'analyse (facultatif).

 Quantity Vérification  Assurez-vous de l'exactitude du type importé.

  Préparation Allouez de la mémoire pour les variables de classe et initialisez-les aux valeurs par défaut.

 Quantity ParsingConvertissez la référence du symbole dans le type en une référence directe.

  (3) Initialisation - Initialisez les variables de classe à la valeur initiale correcte.

Chaque implémentation de machine virtuelle JAVA doit avoir un chargeur de classes de démarrage qui sait comment charger des classes de confiance.

Chaque chargeur de classe possède son propre espace de noms, qui conserve les types chargés par lui. Ainsi, un programme Java peut charger plusieurs fois plusieurs types avec le même nom complet. Le nom complet d'un tel type n'est pas suffisant pour déterminer l'unicité au sein d'une machine virtuelle Java. Par conséquent, lorsque plusieurs chargeurs de classe chargent un type portant le même nom, afin d'identifier le type de manière unique, l'ID du chargeur de classe qui charge le type (indiquant l'espace de noms dans lequel il se trouve) doit être précédé du nom du type.

Zone de méthode

Dans la machine virtuelle Java, les informations sur le type chargé sont stockées dans une mémoire logiquement appelée zone de méthode. Lorsque la machine virtuelle charge un certain type, elle utilise le chargeur de classe pour localiser le fichier de classe correspondant, puis lit le fichier de classe - un flux de données binaires linéaires, puis le transmet à la machine virtuelle, puis la machine virtuelle extrait le type. informations et stockez ces informations dans la zone méthode. Les variables de classe (statiques) de ce type sont également stockées dans la zone méthode.

La manière dont la machine virtuelle JAVA stocke les informations de type en interne est déterminée par le concepteur de l'implémentation spécifique.

Lorsque la machine virtuelle exécute un programme Java, elle recherche et utilise les informations de type stockées dans la zone de méthode. Étant donné que tous les threads partagent la zone de méthode, leur accès aux données de la zone de méthode doit être conçu pour être thread-safe. Par exemple, en supposant que deux threads tentent d'accéder à une classe nommée Lava en même temps et que cette classe n'a pas été chargée dans la machine virtuelle, alors un seul thread devrait la charger à ce moment, tandis que l'autre thread ne peut qu'attendre .

Pour chaque type chargé, la machine virtuelle stockera les informations de type suivantes dans la zone méthode :

● Le nom complet de ce type

● Le nom complet de la superclasse directe de ce type (sauf si le type est java.lang.Object, qui n'a pas de superclasse)

● Ce type est-il une classe type ou interface Type

● Le modificateur d'accès de ce type (un sous-ensemble de public, abstrait ou final)

● L'ensemble complet de toute superinterface directe Une liste ordonnée de noms qualifiés

En plus des informations de type de base répertoriées ci-dessus, la machine virtuelle doit également stocker les informations suivantes pour chaque type chargé :

● Le pool constant de ce type

● Informations sur le terrain

● Informations sur la méthode

QuantityEn plus des constantes Toutes les variables de classe (statiques) sauf 🎜>

 

Constant Pool  La machine virtuelle doit maintenir un pool constant pour chaque type chargé. Un pool de constantes est une collection ordonnée de constantes utilisées par le type, y compris des constantes directes et des références symboliques à d'autres types, champs et méthodes. Les éléments de données d'un pool sont accessibles par index, tout comme un tableau. Étant donné que le pool de constantes stocke les références symboliques à tous les types, champs et méthodes utilisés par le type correspondant, il joue un rôle central dans la liaison dynamique des programmes Java.

  字段信息

  对于类型中声明的每一个字段。方法区中必须保存下面的信息。除此之外,这些字段在类或者接口中的声明顺序也必须保存。

  ○ 字段名

  ○ 字段的类型

  ○ 字段的修饰符(public、private、protected、static、final、volatile、transient的某个子集)

  方法信息

  对于类型中声明的每一个方法,方法区中必须保存下面的信息。和字段一样,这些方法在类或者接口中的声明顺序也必须保存。

  ○ 方法名

  ○ 方法的返回类型(或void)

  ○ 方法参数的数量和类型(按声明顺序)

  ○ 方法的修饰符(public、private、protected、static、final、synchronized、native、abstract的某个子集)

  除了上面清单中列出的条目之外,如果某个方法不是抽象的和本地的,它还必须保存下列信息:

  ○ 方法的字节码(bytecodes)

  ○ 操作数栈和该方法的栈帧中的局部变量区的大小

  ○ 异常表

  类(静态)变量

  类变量是由所有类实例共享的,但是即使没有任何类实例,它也可以被访问。这些变量只与类有关——而非类的实例,因此它们总是作为类型信息的一部分而存储在方法区。除了在类中声明的编译时常量外,虚拟机在使用某个类之前,必须在方法区中为这些类变量分配空间。

  而编译时常量(就是那些用final声明以及用编译时已知的值初始化的类变量)则和一般的类变量处理方式不同,每个使用编译时常量的类型都会复制它的所有常量到自己的常量池中,或嵌入到它的字节码流中。作为常量池或字节码流的一部分,编译时常量保存在方法区中——就和一般的类变量一样。但是当一般的类变量作为声明它们的类型的一部分数据面保存的时候,编译时常量作为使用它们的类型的一部分而保存。

  指向ClassLoader类的引用

  每个类型被装载的时候,虚拟机必须跟踪它是由启动类装载器还是由用户自定义类装载器装载的。如果是用户自定义类装载器装载的,那么虚拟机必须在类型信息中存储对该装载器的引用。这是作为方法表中的类型数据的一部分保存的。

  虚拟机会在动态连接期间使用这个信息。当某个类型引用另一个类型的时候,虚拟机会请求装载发起引用类型的类装载器来装载被引用的类型。这个动态连接的过程,对于虚拟机分离命名空间的方式也是至关重要的。为了能够正确地执行动态连接以及维护多个命名空间,虚拟机需要在方法表中得知每个类都是由哪个类装载器装载的。

  指向Class类的引用

  对于每一个被装载的类型(不管是类还是接口),虚拟机都会相应地为它创建一个java.lang.Class类的实例,而且虚拟机还必须以某种方式把这个实例和存储在方法区中的类型数据关联起来。

  在Java程序中,你可以得到并使用指向Class对象的引用。Class类中的一个静态方法可以让用户得到任何已装载的类的Class实例的引用。

public static Class<?> forName(String className)

  比如,如果调用forName("java.lang.Object"),那么将得到一个代表java.lang.Object的Class对象的引用。可以使用forName()来得到代表任何包中任何类型的Class对象的引用,只要这个类型可以被(或者已经被)装载到当前命名空间中。如果虚拟机无法把请求的类型装载到当前命名空间,那么会抛出ClassNotFoundException异常。

 

  另一个得到Class对象引用的方法是,可以调用任何对象引用的getClass()方法。这个方法被来自Object类本身的所有对象继承:

public final native Class<?> getClass();

  比如,如果你有一个到java.lang.Integer类的对象的引用,那么你只需简单地调用Integer对象引用的getClass()方法,就可以得到表示java.lang.Integer类的Class对象。

  方法区使用实例

  为了展示虚拟机如何使用方法区中的信息,下面来举例说明:

class Lava {private int speed = 5;void flow(){
        
    }
}
public class Volcano {    public static void main(String[] args){
        Lava lava = new Lava();
        lava.flow();
    }
}

  不同的虚拟机实现可能会用完全不同的方法来操作,下面描述的只是其中一种可能——但并不是仅有的一种。

Pour exécuter un programme Volcano, vous devez d'abord indiquer à la machine virtuelle le nom "Volcano" d'une manière "dépendante de l'implémentation". Après cela, la machine virtuelle trouvera et lira le fichier de classe correspondant "Volcano.class", puis extraira les informations de type des données binaires dans le fichier de classe importé et les placera dans la zone de méthode. En exécutant le bytecode enregistré dans la zone de méthode, la machine virtuelle commence à exécuter la méthode main(). Pendant l'exécution, elle contiendra toujours le pool constant (une structure de données dans la zone de méthode) pointant vers la classe actuelle (classe Volcano). aiguille.

Remarque : lorsque la machine virtuelle commence à exécuter le bytecode de la méthode main() dans la classe Volcano, bien que la classe Lava n'ait pas encore été chargée, comme la plupart (peut-être toutes) les implémentations de machines virtuelles, elle ne le fera pas. Attendez que toutes les classes utilisées dans le programme soient chargées avant de l'exécuter. Au contraire, il ne chargera la classe correspondante qu'en cas de besoin.

La première instruction de main() indique à la machine virtuelle d'allouer suffisamment de mémoire pour la classe répertoriée dans le premier élément du pool de constantes. Ainsi, la machine virtuelle utilise le pointeur vers le pool de constantes Volcano pour trouver le premier élément, constate qu'il s'agit d'une référence symbolique à la classe Lava, puis vérifie la zone de méthode pour voir si la classe Lava a été chargée.

Cette référence symbolique est simplement une chaîne donnant le nom complet "Lava" de la classe Lava. Pour que la machine virtuelle puisse trouver une classe à partir d'un nom le plus rapidement possible, le concepteur de la machine virtuelle doit choisir les meilleures structures de données et algorithmes.

Lorsque la machine virtuelle constate que la classe nommée "Lava" n'a pas été chargée, elle commence à rechercher et à charger le fichier "Lava.class", et place les informations de type extraites des données binaires lues dans la zone méthode.

Immédiatement après, la machine virtuelle remplace le premier élément du pool de constantes (c'est-à-dire la chaîne "Lava") par un pointeur pointant directement vers les données de la classe Lava dans la zone méthode. pour accéder rapidement à Lava dans le futur. Ce processus de remplacement est appelé résolution de pool constant, qui remplace les références de symboles dans le pool constant par des références directes.

Enfin, la machine virtuelle est prête à allouer de la mémoire pour un nouvel objet Lava. À ce stade, il a à nouveau besoin des informations de la zone méthode. Vous vous souvenez du pointeur que vous venez de mettre dans le premier élément du pool constant de classe Volcano ? Désormais, la machine virtuelle l'utilise pour accéder aux informations de type Lava et découvrir les informations qui y sont enregistrées : combien d'espace de mémoire un objet Lava doit allouer.

La machine virtuelle JAVA peut toujours déterminer la quantité de mémoire dont un objet a besoin grâce aux informations de type des zones de stockage et de méthode. Lorsque la machine virtuelle JAVA détermine la taille d'un objet Lava, elle lui alloue un espace si grand. le tas et initialisez la vitesse variable de cette instance d'objet à la valeur initiale par défaut 0.

Lorsque la référence de l'objet Lava nouvellement généré est poussée sur la pile, la première instruction de la méthode main() est également terminée. Les instructions suivantes appellent le code Java (qui initialise la variable de vitesse à la valeur initiale correcte de 5) via cette référence. Une autre instruction utilisera cette référence pour appeler la méthode flow() de la référence de l'objet Lava.

Heap

Toutes les instances de classe ou tableaux créés par un programme Java au moment de l'exécution sont placés dans le même tas. Il n'y a qu'un seul espace de tas dans une instance de machine virtuelle JAVA, donc tous les threads partageront ce tas. Et comme un programme Java occupe une instance de machine virtuelle JAVA, chaque programme Java possède son propre espace de mémoire : ils n'interféreront pas les uns avec les autres. Cependant, plusieurs threads du même programme Java partagent le même espace de tas. Dans ce cas, le problème de synchronisation de l'accès multithread aux objets (données du tas) doit être pris en compte.

La machine virtuelle JAVA a une instruction pour allouer de nouveaux objets dans le tas, mais il n'y a pas d'instruction pour libérer de la mémoire, tout comme vous ne pouvez pas libérer explicitement un objet à l'aide de la zone de code Java. La machine virtuelle elle-même est chargée de décider comment et quand libérer la mémoire occupée par les objets qui ne sont plus référencés par les programmes en cours d'exécution. Habituellement, la machine virtuelle laisse cette tâche au garbage collector.

Représentation interne des tableaux

En Java, les tableaux sont de vrais objets. Comme les autres objets, les tableaux sont toujours stockés dans le tas. De même, les tableaux ont une instance Class associée à leur classe, et tous les tableaux ayant les mêmes dimensions et le même type sont des instances de la même classe, quelle que soit la longueur du tableau (la longueur de chaque dimension d'un tableau multidimensionnel). Par exemple, un tableau contenant 3 entiers et un tableau contenant 300 entiers ont la même classe. La longueur du tableau ne concerne que les données d'instance.

Le nom de la classe tableau se compose de deux parties : chaque dimension est représentée par un crochet "[", et un caractère ou une chaîne est utilisé pour représenter le type d'élément. Par exemple, le nom de classe d'un tableau unidimensionnel dont le type d'élément est entier est "[I", le nom de classe d'un tableau tridimensionnel dont le type d'élément est octet est "[[[B", et le nom de classe de un tableau à deux dimensions dont le type d'élément est Object est "[[Ljava/lang/Object".

Les tableaux multidimensionnels sont représentés comme des tableaux de tableaux. Par exemple, un tableau bidimensionnel de type int sera représenté comme un tableau unidimensionnel, dont chaque élément est une référence à un tableau int unidimensionnel, comme indiqué ci-dessous :

 

Chaque objet tableau du tas doit également stocker la longueur du tableau, les données du tableau et une référence aux données de type tableau. La machine virtuelle doit être capable d'obtenir la longueur du tableau grâce à une référence à un objet tableau, d'accéder à ses éléments via des index (au cours desquels les limites du tableau doivent être vérifiées pour voir si elles sont hors limites), d'appeler des méthodes déclarées par le Objet de superclasse direct de tous les tableaux, et ainsi de suite.

Compteur de programme

Pour un programme Java en cours d'exécution, chaque thread qu'il contient possède son propre registre PC (Program Counter), qui est créé au démarrage du thread, la taille du registre PC est de un mot, il peut donc contenir soit un pointeur local, soit une adresse de retour. Lorsqu'un thread exécute une méthode Java, le contenu du registre PC est toujours "l'adresse" de la prochaine instruction à exécuter. L'"adresse" ici peut être un pointeur local, ou elle peut être relative à la méthode dans la méthode. bytecode. Le décalage de l’instruction de départ. Si le thread exécute une méthode native, la valeur du registre PC à ce moment est « indéfini ».

Pile Java

Chaque fois qu'un nouveau thread est démarré, la machine virtuelle Java lui alloue une pile Java. La pile Java enregistre l'état d'exécution du thread dans des frames. La machine virtuelle n'effectuera que deux opérations directement sur la pile Java : pousser et afficher des images.

La méthode exécutée par un thread est appelée la méthode actuelle du thread. Le cadre de pile utilisé par la méthode actuelle est appelé le cadre actuel. La classe à laquelle appartient la méthode actuelle est appelée la classe actuelle. Le pool constant de la classe actuelle est appelé Le pool constant actuel. Lorsqu'un thread exécute une méthode, il garde une trace de la classe actuelle et du pool de constantes actuel. De plus, lorsque la machine virtuelle rencontre une instruction d'opération dans la pile, elle effectue l'opération sur les données de la trame actuelle.

Chaque fois qu'un thread appelle une méthode Java, la machine virtuelle pousse une nouvelle frame dans la pile Java du thread. Et ce nouveau cadre devient naturellement le cadre actuel. Lors de l'exécution de cette méthode, elle utilise ce cadre pour stocker les paramètres, les variables locales, les résultats des opérations intermédiaires et d'autres données.

Les méthodes Java peuvent être appliquées de deux manières. L'un est renvoyé par retour, appelé retour normal ; l'autre se termine anormalement en lançant une exception. Quelle que soit la méthode renvoyée, la machine virtuelle extraira le frame actuel de la pile Java et le libérera, de sorte que le frame de la méthode précédente devienne le frame actuel.

Toutes les données sur le cadre Java sont privées pour ce fil. Aucun thread ne peut accéder aux données de pile d'un autre thread, nous n'avons donc pas besoin de considérer la synchronisation de l'accès aux données de pile dans des situations multithread. Lorsqu'un thread appelle une méthode, les variables locales de la méthode sont enregistrées dans le cadre de pile Java du thread appelant. Un seul thread peut toujours accéder à ces variables locales, le thread qui appelle la méthode.

Pile de méthodes locales

Toutes les zones de données d'exécution mentionnées précédemment sont clairement définies dans la spécification de la machine virtuelle Java. De plus, pour un programme Java en cours d'exécution, il peut également utiliser certaines zones de données associées. méthodes natives. Lorsqu'un thread appelle une méthode native, il entre dans un nouveau monde qui n'est plus restreint par la machine virtuelle. Une méthode native peut accéder à la zone de données d'exécution de la machine virtuelle via l'interface de méthode native, mais plus encore, elle peut faire ce qu'elle veut.

Les méthodes natives dépendent essentiellement de l'implémentation, et les concepteurs d'implémentations de machines virtuelles sont libres de décider quel mécanisme utiliser pour permettre aux programmes Java d'appeler des méthodes natives.

Toute interface de méthode native utilisera une sorte de pile de méthodes natives. Lorsqu'un thread appelle une méthode Java, la machine virtuelle crée un nouveau cadre de pile et le place sur la pile Java. Cependant, lorsqu'elle appelle une méthode locale, la machine virtuelle conserve la pile Java inchangée et ne pousse plus de nouvelle trame dans la pile Java du thread. La machine virtuelle se connecte simplement de manière dynamique et appelle directement la méthode locale spécifiée.

Si l'interface de méthode locale implémentée par une machine virtuelle utilise le modèle de connexion C, alors sa pile de méthodes locale est la pile C. Lorsqu'un programme C appelle une fonction C, ses opérations de pile sont déterminées. Les paramètres transmis à la fonction sont placés sur la pile dans un certain ordre et sa valeur de retour est renvoyée à l'appelant d'une certaine manière. Encore une fois, c'est ainsi que se comporte la pile de méthodes natives dans une implémentation de machine virtuelle.

Il est très probable que l'interface de méthode native doive rappeler la méthode Java dans la machine virtuelle Java. Dans ce cas, le thread enregistrera l'état de la pile de méthodes locale et entrera dans une autre pile Java.

La figure suivante représente un scénario dans lequel lorsqu'un thread appelle une méthode locale, la méthode locale rappelle une autre méthode Java dans la machine virtuelle. Cette image montre une vue panoramique du thread exécuté à l'intérieur de la machine virtuelle JAVA. Un thread peut exécuter des méthodes Java et exploiter sa pile Java tout au long de son cycle de vie ; ou il peut passer sans aucune entrave entre la pile Java et la pile de méthodes natives. ​

Le thread a d'abord appelé deux méthodes Java et la deuxième méthode Java a appelé une méthode native, ce qui a amené la machine virtuelle à utiliser une pile de méthodes natives. Supposons qu'il s'agisse d'une pile de langage C avec deux fonctions C entre les deux. La première fonction C est appelée en tant que méthode native par la deuxième méthode Java, et cette fonction C appelle la deuxième fonction C. Ensuite, la deuxième fonction C rappelle une méthode Java (la troisième méthode Java) via l'interface de méthode locale, et enfin cette méthode Java appelle une méthode Java (elle devient la méthode actuelle dans le diagramme).

Attention aux étudiants qui apprennent Java ! ! !

Si vous rencontrez des problèmes pendant le processus d'apprentissage ou si vous souhaitez obtenir des ressources d'apprentissage, vous êtes invités à rejoindre le groupe d'échange d'apprentissage Java : 299541275 Apprenons Java ensemble !

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!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn