欢迎来到“Under The Hood”第五期。本期我们来看看JVM中处理对象和数组的字节码。你可能需要阅读往期的文章才能更好的理解本文。
面向对象的机器
JVM中的数据有3种形式:对象(object),对象引用(object reference)和原始类型(primitive type)。对象存放在垃圾收集堆中;对象引用和原始类型,根据它们作用域范围的不同,分别存放在不同的地方:作为本地变量,存放在Java栈中;作为实例变量,存放在垃圾收集堆上;作为类变量,存放在方法区上。
在JVM中,垃圾收集堆上只能给对象分配内存空间。原始类型,除了作为对象的一部分,没有其他方式可以给它在堆上分配空间。在需要对象引用的地方,如果你想使用原始类型,你可以给原始类型分配java.lang包中相应的包装对象。只有对象引用和原始类型可以作为本地变量,存放在Java栈中,对象是不可能存放在栈中的。
JVM中,对象和原始类型在架构上的分离,体现在Java编程语言中就是:对象不能被声明成本地变量,只有对象引用才行。在声明时,对象引用不会指向具体对象,只有引用被显式的初始化之后(指向已存在对象,或者通过new关键字创建新对象),引用才会指向实际对象。
在JVM指令集中,除了数组,所有的对象都通过相同的操作符集来实例化和访问。在Java中,数组也是对象,并且就像Java程序中任何其他对象一样,是动态创建的。数组引用可以用在任何需要Object类型的引用的地方,Object中的任何方法,都可以在数组上调用。但是,在JVM里,数组是使用有别于对象的特殊字节码来处理的。
就像任何其他对象一样,数组不能被声明为本地变量;只有数组引用可以。数组对象本身,总是包含一组原始类型或者一组对象引用。如果你声明一个对象数组,你会得到一组对象引用。对象们自己必须用new显式创建,并被赋值给数组的元素。
处理对象的字节码
实例化新对象是通过操作码new来实现的,它需要2个单字节的操作数。这2个单字节操作数合并成16位的常量池索引。常量池中对应的元素给出了新对象的类型信息。就像下面所展示的,JVM在堆上创建新的对象实例,并把它的引用压入栈中。
new | indexbyte1, indexbyte2 | creates a new object on the heap, pushes reference |
接下来的表格,列出了存取对象字段(field)的字节码。操作符putfield和getfield只负责处理实例变量。静态变量使用putstatic和getstatic访问,这个我们待会再说。操作符putfield和getfield都有2个单字节操作数,它们合并成16位的常量池索引。相应的常量池位置上存放着关于字段的类型,大小和偏移量的信息。putfield和getfield都会从栈中取得操作对象的引用。putfield从栈上获取所要赋给实例变量的值,而getfield则把取到的实例变量的值压进栈中。
putfield | indexbyte1, indexbyte2 | set field, indicated by index of object to value (both taken from stack) |
getfield | indexbyte1, indexbyte2 | pushes field, indicated by index of object (taken from stack) |
如下表所示,静态变量通过putstatic和getstatic来访问。它们所拥有的2个单字节的操作数,会由JVM合并成16位的常量池索引。对应的常量池位置上存有静态变量的相关信息。由于静态变量不跟任何对象关联,putstatic和getstatic也不会使用对象引用。putstatic从栈中取得所要附给静态变量的值,而getstatic则把静态变量的值压入栈中。
putstatic | indexbyte1, indexbyte2 | set field, indicated by index, of object to value (both taken from stack) |
getstatic | indexbyte1, indexbyte2 | pushes field, indicated by index, of object (taken from stack) |
下面的操作码用来检查栈顶的对象引用,是不是指向由操作数所索引的类或接口的实例。
当对象不是指定类或接口的实例时,checkcast指令会抛出CheckCastException异常;反之,checkcast什么也不做,对象引用仍保留在栈顶,继续执行下一个指令。checkcast指令保证了运行时的类型转换是安全的,它是JVM安全系统的一部分。
instanceof指令弹出栈顶的对象引用,最后把true或false压入栈中。如果对象确实是指定类或接口的实例,则把true入栈;反之,false入栈。instanceof指令实现了Java语言中的instanceof关键字,它允许程序员测试一个对象是不是某个类或接口的实例。
checkcast | indexbyte1, indexbyte2 | Throws ClassCastException if objectref on stack cannot be cast to class at index |
instanceof | indexbyte1, indexbyte2 | Pushes true if objectref on stack is an instanceof class at index, else pushes false |
处理数组的字节码
可以通过指令newarray,anewarray,和multianewarray实例化数组。
newarray用来创建原始类型的数组,具体类型由它的单字节操作数指定。它可以创建的数组类型有byte,short,char,int,long,float,double和boolean。
anewarray创建对象引用数组,它的2个单字节操作数合并成16位常量池索引,由此获取到要创建的包含在数组中的对象类型信息。anewarray为数组中的对象引用分配控件,并把它们都设置为null。
multianewarray用来分配多维数组(数组的数组),可以通过重复调用newarray和anewarray来实现。multianewarray指令只是简单的把创建多维数组的多条字节码压缩成一个指令。它的开头2个单字节操作数合并成常量池索引,由此获取多维数组中的对象类型。第3个单字节操作数指明了多维数组的维数。至于每维的数组大小需从栈上获取。该指令为多维数组中所有的数组分配空间。
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 |
下表列出的指令把栈顶的数组引用弹出,把它的长度压入栈中。
arraylength | (none) | pops objectref of an array, pushes length of that array |
下面的操作码获取数组中的元素。数组索引和数组引用从栈上弹出,数组中指定索引处的值被压入栈中。
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] |
下表列出了把值存入数组元素中的操作码。值、索引和数组引用均从栈顶弹出。
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 value index, and arrayref of an array of objectrefs, assigns arrayref[index] = value |
本文译自:Objects and Arrays
原创文章,转载请注明: 转载自码农合作社
本文链接地址: 对象和数组:JVM中,处理对象和数组的字节码介绍
相关推荐
首先,Java对象主要存在于JVM的堆内存中,堆是Java程序运行时的主要内存区域,用于存储所有实例对象和数组。堆内存由Java虚拟机自动管理,包括分配和回收。 1. **对象头**:每个Java对象都有一个对象头,它包含两...
Java JVM 是 Java 语言的核心组件之一,负责将 Java 字节码翻译成机器语言并执行。要深入了解 JVM,可以从 Java 的特性入手,描绘 JVM 的大致应用,然后细细阐述 JVM 的原理及内存管理机制和调优。最后,还需要讲述...
10. **类和数组操作指令**:`newarray`用于创建数组,`anewarray`创建引用类型数组,`multianewarray`创建多维数组,`ldc`用于加载常量池中的类名或字符串。 理解JVM指令手册对于优化Java代码、进行内存分析、理解...
5. **对象和数组操作指令**:如`new`创建新对象,`getfield`和`putfield`访问或修改实例字段,`aaload`和`aastore`用于数组元素的加载和存储。 6. **方法调用与返回指令**:`invokevirtual`用于调用对象的虚方法,`...
JVM指令主要分为:本地变量表到操作数栈类指令、操作数栈到本地变量表类指令、常数到操作数栈类指令、将数组指定索引的数组推送至操作数栈类指令、将操作数栈数存储到数组指定索引类指令、操作数栈其他相关类指令、...
3.4.3 第三趟:字节码验证 3.4.4 第四趟:符号引用的验证 3.4.5 二进制兼容 3.5 java虚拟机中内置的安全特性 3.6 安全管理器和java api 3.7 代码签名和认证 3.8 一个代码签名示例 3.9 策略 3.10...
- **对象和数组操作指令**:涉及对象的创建、引用、方法调用以及数组的访问(如`new`创建新对象,`getfield`获取对象字段值,`invokevirtual`调用虚方法,`aaload`取数组元素)。 - **类和接口操作指令**:处理类...
- 堆(Heap):所有对象实例和数组都在堆中分配。 - 方法区(Method Area):存储类和接口的信息,包括运行时常量池。 - 运行时常量池(Run-Time Constant Pool):属于方法区,存储常量和符号引用。 - 本地方法...
JVM虚拟机指令集是Java字节码的基石,它由一系列低级、简单的操作码组成,这些操作码负责执行基本的计算和控制任务。每条指令通常对应一个字节,并且有特定的含义和作用。例如: 1. **加载和存储指令**:如`iload`...
程序计数器记录线程执行的字节码行号,本地方法栈服务于native方法,方法区存储类信息、常量、静态变量等,Java虚拟机栈存储方法的局部变量和操作栈,而Java堆是所有线程共享的内存区域,用于存放对象实例和数组。...
2. **字节码指令**:Java字节码是一系列基于16位的指令,包括加载和存储指令、运算或算术指令、类型转换指令、创建对象和数组的指令等。 - **加载和存储指令**:如`iload`用于从局部变量表加载整数到操作数栈,`...
然后,字节码文件被加载到JVM中执行。JVM负责解释字节码并在目标平台上执行。 **1.4 半编译半解释** Java的执行方式被称为“半编译半解释”。编译是指将源代码转换成字节码的过程,而解释是指JVM动态地执行这些...
堆是 JVM 中的内存区域,用于存储对象实例和数组。 3. 方法区(Method Area) 方法区是 JVM 中的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区是各个线程共享...
- **对象和数组**(Chapter 15)、**控制流**(Chapter 16)、**异常处理**(Chapter 17):这些章节涵盖了Java编程中的核心概念,如对象创建、数组操作、条件语句、循环控制以及异常捕获等。 - **最终子句**...
- **字节码**: Java编译器将源代码编译成中间格式——字节码,这些字节码可以在任何支持JVM的平台上运行。 - **JVM实现**: 不同操作系统上的JVM实现会有所不同,但它们都遵循相同的规范,确保字节码可以正确执行。 ...