Introduction
Au cours des deux derniers jours, un collègue s'est gratté la tête et s'est demandé si Java est passé par valeur ou par référence. Confus au sujet de sa sœur, il a soulevé la question pour que tout le monde en discute. Par conséquent, certaines personnes disent passer par valeur, et d'autres disent passer par référence, quelle que soit la partie qui estime que sa compréhension est correcte. Je pense : pour répondre à cette question, autant mettre cette question de côté et remonter d'abord en amont de cette question : l'allocation de mémoire Java. En ce qui concerne l'allocation de mémoire, je pense qu'une phrase viendra à l'esprit de beaucoup de gens : les références sont placées sur la pile, les objets sont placés sur le tas et la pile pointe vers le tas. Hmm, cette phrase semble correcte, mais continuons à nous demander : quelle est cette pile ? Est-ce l'auberge Longmen ? Non! Il s'agit en fait d'une pile de machines virtuelles Java. Eh bien, à ce stade, les enfants studieux ne peuvent s'empêcher de demander : qu'est-ce que la pile de machines virtuelles Java ? Ne vous inquiétez pas, jetons un coup d'œil ensemble.
Nous savons que : tout programme Java s'exécute sur une machine virtuelle Java c'est-à-dire : un runtime Java La machine virtuelle est ; responsable de l'exécution d'un programme Java. Lorsqu'un programme Java est démarré, une instance de machine virtuelle naît ; lorsque l'exécution du programme est terminée, l'instance de machine virtuelle meurt. Par exemple : si cinq programmes Java sont exécutés simultanément sur un ordinateur, le système fournira cinq instances de machine virtuelle Java ; chaque programme Java s'exécute indépendamment dans sa propre instance de machine virtuelle Java correspondante.
Il existe deux types de threads dans la machine virtuelle Java, à savoir : 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 le garbage collection. Les threads non-démons font généralement référence à nos propres threads. Lorsque tous les threads non démons du programme se terminent, l'instance de machine virtuelle se ferme automatiquement.
Puisque la machine virtuelle Java est responsable de l'exécution des programmes Java, jetons d'abord un coup d'œil à l'architecture de la machine virtuelle Java, s'il vous plaît voir ci-dessous :
Vous pouvez voir ici : le fichier de classe est chargé dans la JVM par le chargeur de classe et exécuté. Ici, nous nous concentrons sur les zones de données d'exécution de la JVM dans le filaire bleu, qui représente la division et l'allocation de l'espace mémoire par la JVM pendant l'exécution. Cette zone de données est divisée en zones principales suivantes : zone de méthode, tas, piles Java, registre de compteur de programme, pile de méthode native, et maintenant. Les principales fonctions et caractéristiques de chaque région sont présentées en détail ci-dessous.
La zone de méthode (zone de méthode) est une zone de mémoire partagée par chaque thread. Elle est utilisée pour stocker les informations de classe qui ont été chargées. par la machine virtuelle, les constantes, les variables statiques, le code compilé par le compilateur et d'autres données. Selon la spécification de la machine virtuelle Java, lorsque la zone de méthode ne peut pas répondre aux exigences d'allocation de mémoire, une exception OutOfMemoryError (OOM) sera levée. Pour mieux comprendre la zone Méthode, examinons les composants spécifiques inclus dans cette zone.
En plus de la version de la classe, des champs, des méthodes, des interfaces et d'autres descriptions, le fichier Classe contient des informations étroitement liées à la classe . , il existe également un pool de constantes utilisé pour stocker divers littéraux et références de symboles générés lors de la compilation ; ce pool de constantes sera stocké dans le pool de constantes d'exécution dans la zone de méthode après le chargement de la classe. En d'autres termes : une collection ordonnée de constantes utilisées par cette classe est stockée dans le pool de constantes d'exécution, qui joue un rôle très important dans la connexion dynamique des programmes Java. Cette collection comprend des constantes directes (chaîne, entier et virgule flottante, etc.) et des références symboliques à d'autres types, champs et méthodes. Le monde extérieur peut accéder aux éléments de données du pool de constantes d'exécution via des index, ce qui est très similaire à l'accès à un tableau. Bien entendu, le pool de constantes d'exécution fait partie de la zone de méthode et il sera également limité par la mémoire de la zone de méthode. Lorsque le pool de constantes d'exécution ne peut plus demander de mémoire, une exception OutOfMemoryError (OOM) sera levée.
Dans cette section, incluez :
Nom complet du type
Le nom complet de la superclasse directe du type
Que le type soit un type classe ou un type interface
Modificateurs d'accès (public, abstrait, final, etc.)
Liste ordonnée des noms complets de superinterfaces directes
Les informations sur le champ sont utilisées pour décrire tous les champs déclarés dans la classe (sauf les variables locales). Elles contiennent les informations spécifiques suivantes :
Nom du champ
Type de champ
Modificateur du champ
Ordre du champ
Les informations sur la méthode sont utilisées pour décrire toutes les méthodes déclarées dans la classe et contiennent les informations spécifiques suivantes :
Nom de la méthode
Type de retour de la méthode
Numéro, type, ordre des paramètres d'entrée de la méthode
Modificateurs de méthode
pile d'opérandes
La taille de la zone de variable locale dans la pile de cadres
Ceci part Utilisé pour stocker les variables modifiées statiquement dans la classe.
La classe est chargée par le chargeur de classe, et la JVM conservera une référence au chargeur de classe dans la zone de méthode .
Pendant le processus de chargement d'une classe par le chargeur, la machine virtuelle crée un objet Class représentant la classe. en même temps La JVM conservera une référence à la classe dans la zone méthode.
Le registre du compteur de programme n'occupe qu'un très petit espace mémoire dans les zones de données d'exécution. Il est utilisé pour stocker l'adresse de la prochaine instruction de bytecode à. être exécuté.
Java Stacks (pile Java) est également appelée la pile de machines virtuelles (VM Stack), qui est ce que nous appelons habituellement la pile . Il est utilisé pour décrire le modèle de mémoire d'exécution des méthodes Java : lorsque chaque méthode est exécutée, un cadre de pile (Stack Frame) est créé en même temps pour stocker des informations telles que des tables de variables locales, des piles d'opérations, des liens dynamiques, des sorties de méthode, etc. Le processus depuis chaque méthode appelée jusqu'à la fin de l'exécution correspond au processus de poussée d'un cadre de pile de la pile vers sa sortie de la pile dans la pile de la machine virtuelle. Le cycle de vie de Java Stacks est le même que celui des threads ; lorsqu'un thread termine son exécution, la pile est également effacée.
Native Method Stack (Native Method Stack) est très similaire à Java Stacks (Java stack). appels à local La table des variables locales, la pile d'opérations et d'autres informations impliquées dans la méthode (C/C++).
Heap (Heap) est créé au démarrage de la machine virtuelle et est utilisé pour stocker les instances d'objets. Presque toutes les instances d'objets allouent de la mémoire ici. Par conséquent, le tas est la plus grande pièce de mémoire gérée par la machine virtuelle Java et constitue également la zone clé gérée par le garbage collector.
Voici un résumé de la zone de données d'exécution de la JVM :
Zone de méthode (zone de méthode) et tas est une zone mémoire partagée par tous les threads.
Java Stacks (pile Java), Program Counter Register (compteur de programme) et Native Method Stack (pile de méthodes natives) sont des zones de mémoire privées pour chaque thread.
Créez un objet, la référence de l'objet est stockée dans Java Stacks (pile Java) et l'instance réelle de l'objet est stockée dans Heap (heap). C’est ce que tout le monde dit souvent : la pile pointe vers le tas.
En plus de la mémoire impliquée dans la zone de données d'exécution JVM que nous venons de mentionner, nous devons également faire attention à la mémoire directe (Direct Memory). Remarque : La mémoire directe (Direct Memory) ne fait pas partie de la zone de données d'exécution de la machine virtuelle et n'est pas non plus une zone de mémoire définie dans la spécification de la machine virtuelle Java, mais cette partie de la mémoire est également fréquemment utilisée et peut également provoquer une erreur OutOfMemoryError (MOO). ) Une exception se produit. Par exemple, lors de l'utilisation de NIO, il peut utiliser la bibliothèque de fonctions natives pour allouer directement de la mémoire hors tas, puis opérer via l'objet DirectByteBuffer stocké dans le tas Java comme référence à cette mémoire. Des opérations similaires peuvent éviter de copier des données entre le tas Java et le tas natif, améliorant ainsi les performances.
Lors de l'appel d'une méthode Java pour transmettre des paramètres, est-elle transmise par en valeur ou en valeur ? Qu'en est-il des cotations ? Face à de nombreux débats, penchons-nous sur le code. Après tout, le code ne ment pas. Regardons d'abord un exemple très simple : en échangeant deux données de type int, le code est le suivant :
package cn.com;/** */public class TestMemory { public static void main(String[] args) { TestMemory testMemory=new TestMemory(); int number1=9527; int number2=1314; System.out.println("main方法中,数据交换前:number1="+number1+" , number2="+number2); testMemory.swapData(number1, number2); System.out.println("main方法中,数据交换后:number1="+number1+" , number2="+number2); } private void swapData(int a,int b) { System.out.println("swapData方法中,数据交换前:a="+a+" , b="+b); int temp=a; a=b; b=temp; System.out.println("swapData方法中,数据交换后:a="+a+" , b="+b); } }
Les deux variables number1=9527, number2=1314 que nous avons déclarées dans la méthode main puis These ; deux nombres sont passés en paramètres à la méthode swapData(int a, int b), et les données sont échangées au sein de la méthode. Quant au code lui-même, il n’est pas nécessaire de trop expliquer, mais pensez au résultat final ? Après y avoir réfléchi, veuillez consulter les informations imprimées suivantes :
Dans la méthode principale, avant l'échange de données : number1=9527, number2=1314
Dans la méthode swapData, avant échange de données : a=9527, b=1314
Dans la méthode swapData, après échange de données : a=1314, b=9527
Dans la méthode principale, après échange de données : number1=9527, number2=1314
嗯哼,这和你想的一样么?为什么会是这样呢?还记得刚才讨论Java Stacks(Java 栈)时说的么:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。结合示例的代码:main( )方法在一个栈帧中,swapData( )在另外一个栈帧中;两者彼此独立互不干扰。在main( )中调用swapData( )传入参数时它的本质是:将实际参数值的副本(复制品)传入其它方法内而参数本身不会受到任何影响。也就是说,这number1和number2这两个变量仍然存在于main( )方法所对应的栈帧中,但number1和number2这两个变量的副本(即int a和int b)存在于swapData( )方法所对应的栈帧中。故,在swapData( )中交换数据,对于main( )是没有任何影响的。这就是Java中调用方法时的传值机制——值传递。
嗯哼,刚才这个例子是关于基本类型的参数传递。Java对于引用类型的参数传递一样采用了值传递的方式。我们在刚才的示例中稍加改造。首先,我们创建一个类,该类有两个变量number1和number2,请看代码:
package cn.com;/** */public class DataObject { private int number1; private int number2; public int getNumber1() { return number1; } public void setNumber1(int number1) { this.number1 = number1; } public int getNumber2() { return number2; } public void setNumber2(int number2) { this.number2 = number2; } }
好了,现在我们来测试交换DataObject类对象中的两个数据:
package cn.com;/** */public class TestMemory { public static void main(String[] args) { TestMemory testMemory=new TestMemory(); DataObject dataObject=new DataObject(); dataObject.setNumber1(9527); dataObject.setNumber2(1314); System.out.println("main方法中,数据交换前:number1="+dataObject.getNumber1()+" , number2="+dataObject.getNumber2()); testMemory.swapData(dataObject); System.out.println("main方法中,数据交换后:number1="+dataObject.getNumber1()+" , number2="+dataObject.getNumber2()); } private void swapData(DataObject dataObject) { System.out.println("swapData方法中,数据交换前:number1="+dataObject.getNumber1()+" , number2="+dataObject.getNumber2()); int temp=dataObject.getNumber1(); dataObject.setNumber1(dataObject.getNumber2()); dataObject.setNumber2(temp); System.out.println("swapData方法中,数据交换后:number1="+dataObject.getNumber1()+" , number2="+dataObject.getNumber2()); } }
简单地描述一下代码:在main( )中定义一个DataObject类的对象并为其number1和number2赋值;然后调用swapData(DataObject dataObject)方法,在该方法中交换数据。请思考输出的结果是什么?在您考虑之后,请参见如下打印信息:
main方法中,数据交换前:number1=9527 , number2=1314
swapData方法中,数据交换前:number1=9527 , number2=1314
swapData方法中,数据交换后:number1=1314 , number2=9527
main方法中,数据交换后:number1=1314 , number2=9527
嗯哼,为什么是这样呢?我们通过DataObject dataObject=new DataObject();创建一个对象;该对象的引用dataObject存放于栈中,而该对象的真正的实例存放于堆中。在main( )中调用swapData( )方法传入dataObject作为参数时仍然传递的是值,只不过稍微特殊点的是:该值指向了堆中的实例对象。好了,再结合栈帧来梳理一遍:main( )方法存在于与之对应的栈帧中,在该栈帧中有一个变量dataObject它指向了堆内存中的真正的实例对象。swapData( )收到main( )传递过来的变量dataObject时将其存放于其本身对应的栈帧中,但是该变量依然指向堆内存中的真正的实例对象。也就是说:main( )方法中的dataObject和swapData( )方法中的dataObject指向了堆中的同一个实例对象!所以,在swapData( )中交换了数据之后,在main( )会体现交换后的变化。在此,我们可以进一步的验证:在该swapData( )方法的最后一行添加一句代码dataObject=null ;我们发现打印信息并没有任何变化。因为这句代码仅仅使得swapData( )所对应的栈帧中的dataObject不再指向堆内存中的实例对象但不会影响main( )所对应的栈帧中的dataObject依然指向堆内存中的实例对象。
通过这两个示例,我们进一步验证了:Java中调用方法时的传递机制——值传递。当然,有的人说:基础类型传值,对象类型传引用。其实,这也没有什么错,只不过是表述方式不同罢了;只要明白其中的道理就行。如果,有些童鞋非纠缠着个别字眼不放,那我只好说:PHP是世界上最好的语言。
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!