Einführung
In den letzten zwei Tagen kratzte sich ein Kollege am Kopf und fragte sich, ob Java als Wert oder als Referenz übergeben wird. Er war verwirrt über seine Schwester und brachte das Thema allen zur Diskussion. Daher sagen einige Leute, dass sie nach Wert gehen, und andere, dass sie nach Referenz gehen; unabhängig davon, welche Partei ihr Verständnis für richtig hält. Ich denke: Um diese Frage zu beantworten, können wir sie genauso gut beiseite legen und zuerst dieser Frage nachgehen – der Java-Speicherzuweisung. Wenn es um die Speicherzuweisung geht, denke ich, dass vielen Menschen ein Satz in den Sinn kommt: Referenzen werden auf dem Stapel platziert, Objekte werden auf dem Heap platziert und der Stapel zeigt auf den Heap. Hmm, dieser Satz klingt richtig; aber fragen wir weiter: Was ist das für ein Stapel? Ist es das Longmen Inn? NEIN! Es handelt sich tatsächlich um einen Java Virtual Machine Stack. Nun, an diesem Punkt müssen die fleißigen Kinder fragen: Was ist der Java Virtual Machine Stack? Machen Sie sich keine Sorgen, schauen wir uns das gemeinsam an.
Wir wissen, dass: jedes Java-Programm auf einer Java Virtual Machine läuft; das heißt: eine Laufzeit-Java Die virtuelle Maschine ist verantwortlich für die Ausführung eines Java-Programms. Wenn ein Java-Programm gestartet wird, wird eine Instanz einer virtuellen Maschine geboren. Wenn die Ausführung des Programms abgeschlossen ist, stirbt die Instanz der virtuellen Maschine. Beispiel: Wenn fünf Java-Programme gleichzeitig auf einem Computer ausgeführt werden, stellt das System fünf Java Virtual Machine-Instanzen bereit; jedes Java-Programm läuft unabhängig in seiner eigenen entsprechenden Java Virtual Machine-Instanz.
In der Java Virtual Machine gibt es zwei Arten von Threads, nämlich Daemon-Threads und Nicht-Daemon-Threads. Daemon-Threads werden normalerweise von der virtuellen Maschine selbst verwendet, z. B. Threads, die die Speicherbereinigung durchführen. Nicht-Daemon-Threads beziehen sich normalerweise auf unsere eigenen Threads. Wenn alle Nicht-Daemon-Threads im Programm beendet werden, wird die Instanz der virtuellen Maschine automatisch beendet.
Da die Java Virtual Machine für die Ausführung von Java-Programmen verantwortlich ist, werfen wir bitte zunächst einen Blick auf die Architektur der Java Virtual Machine siehe unten:
Sie können hier sehen: Die Klassendatei wird vom Klassenlader in die JVM geladen und ausgeführt. Hier konzentrieren wir uns auf die Laufzeitdatenbereiche der JVM im blauen Drahtmodell, das die Aufteilung und Zuweisung von Speicherplatz durch die JVM während der Laufzeit darstellt. Dieser Datenbereich ist in die folgenden Hauptbereiche unterteilt: Methodenbereich (Methodenbereich), Heap (Heap), Java-Stacks (Java-Stack), Programmzählerregister (Programmzähler), Native Method Stack (lokaler Methodenstapel), jetzt Der Hauptbereich Die Funktionen und Merkmale der einzelnen Regionen werden im Folgenden ausführlich vorgestellt.
Der Methodenbereich (Methodenbereich) ist ein von jedem Thread gemeinsam genutzter Speicherbereich. Er wird zum Speichern geladener Klasseninformationen verwendet durch die virtuelle Maschine, Konstanten, statische Variablen, vom Compiler kompilierter Code und andere Daten. Gemäß der Java Virtual Machine-Spezifikation wird eine OutOfMemoryError (OOM)-Ausnahme ausgelöst, wenn der Methodenbereich die Speicherzuweisungsanforderungen nicht erfüllen kann. Um den Methodenbereich besser zu verstehen, schauen wir uns die spezifischen Komponenten an, die in diesem Bereich enthalten sind.
Neben der Klassenversion, Feldern, Methoden, Schnittstellen und anderen Beschreibungen enthält die Klassendatei Informationen, die eng mit der Klasse verbunden sind . Es gibt auch einen Konstantenpool, der zum Speichern verschiedener Literale und Symbolreferenzen verwendet wird, die während der Kompilierung generiert werden. Dieser Konstantenpool wird nach dem Laden der Klasse im Laufzeitkonstantenpool gespeichert. Mit anderen Worten: Eine geordnete Sammlung von Konstanten, die von dieser Klasse verwendet werden, wird im Laufzeitkonstantenpool gespeichert, der bei der dynamischen Verbindung von Java-Programmen eine sehr wichtige Rolle spielt. In dieser Sammlung sind direkte Konstanten (String, Ganzzahl und Gleitkomma usw.) und symbolische Verweise auf andere Typen, Felder und Methoden enthalten. Die Außenwelt kann über Indizes auf Datenelemente im Laufzeitkonstantenpool zugreifen, was dem Zugriff auf ein Array sehr ähnlich ist. Natürlich ist der Laufzeitkonstantenpool Teil des Methodenbereichs und wird auch durch den Speicher des Methodenbereichs begrenzt. Wenn der Laufzeitkonstantenpool keinen Speicher mehr beantragen kann, wird eine OutOfMemoryError-Ausnahme (OOM) ausgelöst.
In diesem Abschnitt sind enthalten:
Vollständig qualifizierter Name des Typs
Der vollständig qualifizierte Name der direkten Oberklasse des Typs
Ob es sich bei dem Typ um einen Klassentyp oder einen Schnittstellentyp handelt
Zugriffsmodifikatoren (öffentlich, abstrakt, endgültig usw.)
Geordnete Liste vollständig qualifizierter Namen direkter Superschnittstellen
Feldinformationen werden zur Beschreibung aller in der Klasse deklarierten Felder verwendet (außer lokalen Variablen). Sie enthalten die folgenden spezifischen Informationen:
Feldname
Feldtyp
Modifikator des Feldes
Reihenfolge des Feldes
Methodeninformationen werden zur Beschreibung aller in der Klasse deklarierten Methoden verwendet und enthalten die folgenden spezifischen Informationen:
Methodenname
Rückgabetyp der Methode
Anzahl, Typ, Reihenfolge der Methodeneingabeparameter
Methodenmodifikatoren
Operandenstapel
Die Größe des lokalen Variablenbereichs im Frame-Stapel
Dies Teil Wird zum Speichern statisch geänderter Variablen in der Klasse verwendet.
Die Klasse wird vom Klassenlader geladen und die JVM behält einen Verweis auf den Klassenlader im Methodenbereich bei .
Während des Ladevorgangs einer Klasse erstellt die virtuelle Maschine ein Klassenobjekt, das die Klasse darstellt Gleichzeitig behält die JVM einen Verweis auf die Klasse im Methodenbereich bei.
Das Programmzählerregister belegt nur einen sehr kleinen Speicherplatz in den Laufzeitdatenbereichen. Es wird zum Speichern der Adresse des nächsten Bytecode-Befehls verwendet ausgeführt werden.
Java Stacks (Java Stack) wird auch als Virtual Machine Stack (VM Stack) bezeichnet, was wir normalerweise als Stack bezeichnen . Es wird verwendet, um das Speichermodell der Java-Methodenausführung zu beschreiben: Wenn jede Methode ausgeführt wird, wird gleichzeitig ein Stapelrahmen (Stack Frame) erstellt, um Informationen wie lokale Variablentabellen, Operationsstapel, dynamische Links, Methodenausgänge usw. zu speichern. usw. Der Prozess vom Aufruf jeder Methode bis zum Abschluss der Ausführung entspricht dem Prozess, bei dem ein Stapelrahmen vom Stapel verschoben und aus dem Stapel im Stapel der virtuellen Maschine herausgenommen wird. Der Lebenszyklus von Java-Stacks ist derselbe wie der von Threads; wenn ein Thread die Ausführung abschließt, wird auch der Stack geleert.
Native Method Stack (Native Method Stack) ist Java Stacks (Java Stack) sehr ähnlich Aufrufe von local Die lokale Variablentabelle, der Operationsstapel und andere an der Methode beteiligte Informationen (C/C++).
Heap (Heap) wird beim Start der virtuellen Maschine erstellt und zum Speichern von Objektinstanzen verwendet. Daher ist der Heap der größte von der Java Virtual Machine verwaltete Speicherbereich und auch der vom Garbage Collector verwaltete Schlüsselbereich.
Hier ist eine Zusammenfassung des JVM-Laufzeitdatenbereichs:
Methodenbereich (Methodenbereich) und Heap ist ein Speicherbereich, der von allen Threads gemeinsam genutzt wird.
Java Stacks (Java-Stack), Program Counter Register (Programmzähler) und Native Method Stack (nativer Methodenstapel) sind private Speicherbereiche für jeden Thread.
Erstellen Sie ein Objekt, die Referenz des Objekts wird in Java Stacks (Java Stack) gespeichert und die reale Objektinstanz wird im Heap (Heap) gespeichert. Das sagt jeder oft: Der Stapel zeigt auf den Heap.
Zusätzlich zu dem gerade erwähnten Speicher im JVM-Laufzeitdatenbereich müssen wir auch auf den direkten Speicher (Direct Memory) achten. Bitte beachten Sie: Der direkte Speicher (Direct Memory) ist weder Teil des Laufzeitdatenbereichs der virtuellen Maschine noch ein in der Java Virtual Machine-Spezifikation definierter Speicherbereich. Dieser Teil des Speichers wird jedoch ebenfalls häufig verwendet und kann auch OutOfMemoryError (OOM) verursachen ) Eine Ausnahme tritt auf. Wenn Sie beispielsweise NIO verwenden, können Sie mithilfe der nativen Funktionsbibliothek direkt Off-Heap-Speicher zuweisen und dann über das im Java-Heap gespeicherte DirectByteBuffer-Objekt als Referenz auf diesen Speicher arbeiten. Ähnliche Vorgänge können das Hin- und Herkopieren von Daten zwischen dem Java-Heap und dem nativen Heap vermeiden und so die Leistung verbessern.
Wenn eine Java-Methode zum Übergeben von Parametern aufgerufen wird, wird diese übergeben? Wert oder nach Wert? Was ist mit Anführungszeichen? Werfen wir angesichts vieler Debatten einen Blick auf den Code. Schließlich lügt der Code nicht. Schauen wir uns zunächst ein sehr einfaches Beispiel an: Beim Austausch zweier int-Daten lautet der Code wie folgt:
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); } }
Die beiden Variablen Nummer1=9527, Nummer2=1314, die wir in der Hauptmethode deklariert haben, und dann Diese Der Methode swapData(int a, int b) werden zwei Zahlen als Parameter übergeben und die Daten innerhalb der Methode ausgetauscht. Was den Code selbst betrifft, gibt es keinen Grund, zu viel zu erklären. Denken Sie jedoch bitte an das Ausgabeergebnis. Nachdem Sie darüber nachgedacht haben, sehen Sie sich bitte die folgenden gedruckten Informationen an:
In der Hauptmethode vor dem Datenaustausch: Nummer1=9527, Nummer2=1314
In der swapData-Methode vor dem Datenaustausch: a=9527, b=1314
In der swapData-Methode nach dem Datenaustausch: a=1314, b=9527
In der Hauptmethode nach dem Datenaustausch: Nummer1=9527, Nummer2=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是世界上最好的语言。
Das obige ist der detaillierte Inhalt vonErkundung der Java-Speicherzuordnung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!