Objekte und Arrays

Willkommen zu einer weiteren Ausgabe von Under The Hood . Diese Spalte konzentriert sich auf die zugrunde liegenden Technologien von Java. Ziel ist es, Entwicklern einen Einblick in die Mechanismen zu geben, mit denen ihre Java-Programme ausgeführt werden. Der Artikel dieses Monats befasst sich mit den Bytecodes, die sich mit Objekten und Arrays befassen.

Objektorientierte Maschine

Die Java Virtual Machine (JVM) arbeitet mit Daten in drei Formen: Objekte, Objektreferenzen und primitive Typen. Objekte befinden sich auf dem Müllhaufen. Objektreferenzen und primitive Typen befinden sich entweder auf dem Java-Stapel als lokale Variablen, auf dem Heap als Instanzvariablen von Objekten oder im Methodenbereich als Klassenvariablen.

In der virtuellen Java-Maschine wird der Speicher auf dem durch Müll gesammelten Heap nur als Objekte zugewiesen. Es gibt keine Möglichkeit, Speicher für einen primitiven Typ auf dem Heap zuzuweisen, außer als Teil eines Objekts. Wenn Sie einen primitiven Typ verwenden möchten, für den eine ObjectReferenz benötigt wird, können Sie dem java.langPaket aus dem Paket ein Wrapper-Objekt zuweisen . Beispielsweise gibt es eine IntegerKlasse, die einen intTyp mit einem Objekt umschließt. Nur Objektreferenzen und primitive Typen können sich als lokale Variablen auf dem Java-Stapel befinden. Objekte können sich niemals auf dem Java-Stack befinden.

Die architektonische Trennung von Objekten und primitiven Typen in der JVM spiegelt sich in der Programmiersprache Java wider, in der Objekte nicht als lokale Variablen deklariert werden können. Nur Objektreferenzen können als solche deklariert werden. Bei der Deklaration bezieht sich eine Objektreferenz auf nichts. Erst nachdem die Referenz explizit initialisiert wurde - entweder mit einer Referenz auf ein vorhandenes Objekt oder mit einem Aufruf von new-, bezieht sich die Referenz auf ein tatsächliches Objekt.

Im JVM-Befehlssatz werden alle Objekte mit Ausnahme von Arrays mit demselben Satz von Opcodes instanziiert und aufgerufen. In Java sind Arrays vollwertige Objekte und werden wie jedes andere Objekt in einem Java-Programm dynamisch erstellt. Array-Referenzen können überall dort verwendet werden, wo eine Referenz auf einen Typ erforderlich Objectist, und jede Methode von Objectkann für ein Array aufgerufen werden. In der virtuellen Java-Maschine werden Arrays jedoch mit speziellen Bytecodes behandelt.

Wie bei jedem anderen Objekt können Arrays nicht als lokale Variablen deklariert werden. Nur Array-Referenzen können. Array-Objekte selbst enthalten immer entweder ein Array primitiver Typen oder ein Array von Objektreferenzen. Wenn Sie ein Array von Objekten deklarieren, erhalten Sie ein Array von Objektreferenzen. Die Objekte selbst müssen explizit mit newden Elementen des Arrays erstellt und diesen zugewiesen werden.

Opcodes für Objekte

Die Instanziierung neuer Objekte erfolgt über das

new

Opcode. Zwei Ein-Byte-Operanden folgen dem

new

Opcode. Diese beiden Bytes werden kombiniert, um einen 16-Bit-Index im konstanten Pool zu bilden. Das konstante Poolelement am angegebenen Versatz gibt Auskunft über die Klasse des neuen Objekts. Die JVM erstellt eine neue Instanz des Objekts auf dem Heap und überträgt den Verweis auf das neue Objekt wie unten gezeigt auf den Stapel.

Objekterstellung
Opcode Operand (en) Beschreibung
new Indexbyte1, Indexbyte2 erstellt ein neues Objekt auf dem Heap, drückt Referenz

Die nächste Tabelle zeigt die Opcodes, die Objektfelder setzen und abrufen. Diese Opcodes putfield und getfield arbeiten nur mit Feldern, die Instanzvariablen sind. Auf statische Variablen wird über putstatic und getstatic zugegriffen, die später beschrieben werden. Die Anweisungen putfield und getfield nehmen jeweils zwei Ein-Byte-Operanden auf. Die Operanden werden kombiniert, um einen 16-Bit-Index im konstanten Pool zu bilden. Das konstante Poolelement an diesem Index enthält Informationen zu Typ, Größe und Versatz des Felds. Die Objektreferenz wird sowohl in den Anweisungen putfield als auch getfield aus dem Stapel entnommen. Der Befehl putfield nimmt den Wert der Instanzvariablen vom Stapel, und der Befehl getfield schiebt den abgerufenen Wert der Instanzvariablen auf den Stapel.

Zugriff auf Instanzvariablen
Opcode Operand (en) Beschreibung
putfield Indexbyte1, Indexbyte2 Set-Feld, angezeigt durch Index, von Objekt zu Wert (beide vom Stapel genommen)
getfield Indexbyte1, Indexbyte2 schiebt das durch den Index angegebene Feld des Objekts (vom Stapel genommen)

Auf Klassenvariablen wird über die Opcodes getstatic und putstatic zugegriffen, wie in der folgenden Tabelle gezeigt. Sowohl getstatic als auch putstatic verwenden zwei Ein-Byte-Operanden, die von der JVM kombiniert werden, um einen vorzeichenlosen 16-Bit-Offset in den konstanten Pool zu bilden. Das konstante Poolelement an dieser Position gibt Informationen zu einem statischen Feld einer Klasse. Da einem statischen Feld kein bestimmtes Objekt zugeordnet ist, gibt es keine Objektreferenz, die von getstatic oder putstatic verwendet wird. Die putstatische Anweisung übernimmt den zuzuweisenden Wert aus dem Stapel. Der Befehl getstatic schiebt den abgerufenen Wert auf den Stapel.

Zugriff auf Klassenvariablen
Opcode Operand (en) Beschreibung
putstatic Indexbyte1, Indexbyte2 Set-Feld, angezeigt durch Index, von Objekt zu Wert (beide vom Stapel genommen)
getstatic Indexbyte1, Indexbyte2 schiebt das durch den Index angegebene Feld des Objekts (vom Stapel genommen)

Die folgenden Opcodes prüfen, ob sich die Objektreferenz oben im Stapel auf eine Instanz der Klasse oder Schnittstelle bezieht, die von den Operanden nach dem Opcode indiziert wird. Die Checkcast-Anweisung wird ausgelöst, CheckCastExceptionwenn das Objekt keine Instanz der angegebenen Klasse oder Schnittstelle ist. Ansonsten macht checkcast nichts. Die Objektreferenz bleibt auf dem Stapel und die Ausführung wird bei der nächsten Anweisung fortgesetzt. Diese Anweisung stellt sicher, dass Casts zur Laufzeit sicher sind und Teil der Sicherheitsdecke der JVM sind.

Die Instanz der Anweisung entfernt die Objektreferenz vom oberen Rand des Stapels und gibt true oder false aus. Wenn das Objekt tatsächlich eine Instanz der angegebenen Klasse oder Schnittstelle ist, wird true auf den Stapel verschoben, andernfalls wird false auf den Stapel übertragen. Die Instanz der Anweisung wird verwendet, um das instanceofSchlüsselwort Java zu implementieren , mit dem Programmierer testen können, ob ein Objekt eine Instanz einer bestimmten Klasse oder Schnittstelle ist.

Typprüfung
Opcode Operand (en) Beschreibung
checkcast Indexbyte1, Indexbyte2 Löst eine ClassCastException aus, wenn objectref on stack beim Index nicht in class umgewandelt werden kann
instanceof Indexbyte1, Indexbyte2 Gibt true aus, wenn objectref on stack eine Instanz der Klasse im Index ist, andernfalls false

Opcodes für Arrays

Die Instanziierung neuer Arrays erfolgt über die Opcodes newarray, anewarray und multianewarray. Der Newarray-Opcode wird verwendet, um Arrays anderer primitiver Typen als Objektreferenzen zu erstellen. Der bestimmte primitive Typ wird durch einen einzelnen Ein-Byte-Operanden angegeben, der dem newarray-Opcode folgt. Der Befehl newarray kann Arrays für Byte, Short, Char, Int, Long, Float, Double oder Boolean erstellen.

Die Anweisung anewarray erstellt ein Array von Objektreferenzen. Zwei Ein-Byte-Operanden folgen dem Anewarray-Opcode und werden kombiniert, um einen 16-Bit-Index im konstanten Pool zu bilden. Eine Beschreibung der Objektklasse, für die das Array erstellt werden soll, befindet sich im Konstantenpool am angegebenen Index. Diese Anweisung weist Platz für das Array von Objektreferenzen zu und initialisiert die Referenzen auf null.

The multianewarray instruction is used to allocate multidimensional arrays -- which are simply arrays of arrays -- and could be allocated with repeated use of the anewarray and newarray instructions. The multianewarray instruction simply compresses the bytecodes needed to create multidimensional arrays into one instruction. Two one-byte operands follow the multianewarray opcode and are combined to form a 16-bit index into the constant pool. A description of the class of object for which the array is to be created is found in the constant pool at the specified index. Immediately following the two one-byte operands that form the constant pool index is a one-byte operand that specifies the number of dimensions in this multidimensional array. The sizes for each dimension are popped off the stack. This instruction allocates space for all arrays that are needed to implement the multidimensional arrays.

Creating new arrays
Opcode Operand(s) Description
newarray atype pops length, allocates new array of primitive types of type indicated by atype, pushes objectref of new array
anewarray indexbyte1, indexbyte2 pops length, allocates a new array of objects of class indicated by indexbyte1 and indexbyte2, pushes objectref of new array
multianewarray indexbyte1, indexbyte2, dimensions pops dimensions number of array lengths, allocates a new multidimensional array of class indicated by indexbyte1 and indexbyte2, pushes objectref of new array

The next table shows the instruction that pops an array reference off the top of the stack and pushes the length of that array.

Getting the array length
Opcode Operand(s) Description
arraylength (none) pops objectref of an array, pushes length of that array

The following opcodes retrieve an element from an array. The array index and array reference are popped from the stack, and the value at the specified index of the specified array is pushed back onto the stack.

Retrieving an array element
Opcode Operand(s) Description
baload (none) pops index and arrayref of an array of bytes, pushes arrayref[index]
caload (none) pops index and arrayref of an array of chars, pushes arrayref[index]
saload (none) pops index and arrayref of an array of shorts, pushes arrayref[index]
iaload (none) pops index and arrayref of an array of ints, pushes arrayref[index]
laload (none) pops index and arrayref of an array of longs, pushes arrayref[index]
faload (none) pops index and arrayref of an array of floats, pushes arrayref[index]
daload (none) pops index and arrayref of an array of doubles, pushes arrayref[index]
aaload (none) pops index and arrayref of an array of objectrefs, pushes arrayref[index]

The next table shows the opcodes that store a value into an array element. The value, index, and array reference are popped from the top of the stack.

Storing to an array element
Opcode Operand(s) Description
bastore (none) pops value, index, and arrayref of an array of bytes, assigns arrayref[index] = value
castore (none) pops value, index, and arrayref of an array of chars, assigns arrayref[index] = value
sastore (none) pops value, index, and arrayref of an array of shorts, assigns arrayref[index] = value
iastore (none) pops value, index, and arrayref of an array of ints, assigns arrayref[index] = value
lastore (none) pops value, index, and arrayref of an array of longs, assigns arrayref[index] = value
fastore (none) pops value, index, and arrayref of an array of floats, assigns arrayref[index] = value
dastore (none) pops value, index, and arrayref of an array of doubles, assigns arrayref[index] = value
aastore (none) Pops Wert, Index und ArrayRef eines Arrays von Objektrefs, weist ArrayRef [Index] = Wert zu

Dreidimensionales Array: eine Java Virtual Machine-Simulation

Das folgende Applet zeigt eine virtuelle Java-Maschine, die eine Folge von Bytecodes ausführt. Die Bytecode-Sequenz in der Simulation wurde javacfür die initAnArray()Methode der unten gezeigten Klasse generiert :

Klasse ArrayDemo {static void initAnArray () {int [] [] [] threeD = new int [5] [4] [3]; für (int i = 0; i <5; ++ i) {für (int j = 0; j <4; ++ j) {für (int k = 0; k <3; ++ k) {threeD [ i] [j] [k] = i + j + k; }}}}}

Die von javacfor generierten Bytecodes initAnArray()werden unten angezeigt: