在java虚拟机中,内存只能以对象形式在垃圾收集堆中分配。除非作为对象的一部分,否则不能为基本类型在堆中分配内存。如果需要在必须使用对象引用的场合使用基本类型,可以从java.lang包中为类型分配一个包装器对象。
只有对象引用和基本类型可以在java的栈中已局部变量形式存在。java栈不能容纳对象。
java虚拟机中的对象和基本类型的结构分离,在java编程语言中体现为:对象不能作为局部变量声明,只有对象引用和基本类型可以;对象引用在声明后并不指向任何有意义的东西,只有在引用被显示初始化后(无论是使引用指向一个已存在的对象还是新建一个对象)对象引用才会指向一个真实的对象
在java虚拟机指令集中,除了数组以外,所有的对象都使用同样的操作码来实例化和存取。和java中其他对象一样,数组是动态创建的,数组可以在任何需要用到引用标识对象的地方使用,数组中的任何方法都可以被调用,但java虚拟机中仍用特殊的字节码来处理数组。
如同其他的对象,数组不能作为局部变量来使用,只有数组引用才可以。数组对象本身通常包含基本类型数组和对象引用数组。如果声明了对象数组,获得的将是对象引用的数组。对象本身必须通过new操作显式创建,并且赋给数组成员。
1、针对对象的操作码
实例化一个新对象需要通过new操作码来实现。new操作码后面紧随着两个字长的操作数,这两个操作数合起来表示常量池中一个不带符号的16位长度的索引。在特定偏移量位置处的常量池入口给出了新对象所属类的信息。如果还没有这些信息,那么虚拟机会解析这个常量池入口。它会为这个堆中的对象建立一个新的实例,用默认初始化对象实例变量,然后把新对象的引用压入栈。
对象的创建
操作码 | 操作数 | 说明 |
new | indexbyte1,indextype2 | 在堆中创建一个新的对象,将其引用压入栈 |
存取实例变量,putfield和getfield这两个操作码只在字段是实例变量的情况下才执行,putfield和getfield都有两个操作数,这两个操作数合起来表示常量池中的不带符号的16位长度的索引。这个索引所指向的常量池入口包含了该字段的所属类、名字和类型等信息。如果还没有这些信息,虚拟机会解析这个常量池入口。putfield和getfield对对象引用进行栈操作。
操作码 | 操作数 | 说明 |
putfield | indexbyte1,indextype2 | 设置对象字段(由index指定)的值,值value和对象引用objectref均从栈中获得 |
getfield | indexbyte1,indextype2 | 将对象字段(由index指定)压入栈,对象引用objectref栈中取得 |
存取类变量,putstatic和getstatic对静态变量进行存取操作。操作数表示常量池的索引。putstatic和getstatic都有两个长度以为一个字长的操作数,这两个操作数合起来表示常量池中的不带符号的16位的偏移量。该位置处的常量池给出了一个类的静态字段的相关信息。如果还没有这些信息,虚拟机会解析这个常量池入口。因为没有任何特定的对象与静态字段相关联,所以getstatic和putstatic不会使用对象引用。
操作码 | 操作数 | 说明 |
putstatic | indexbyte1,indextype2 | 设置静态字段(由index指定)的值,值value从栈中获得 |
getstatic | indexbyte1,indextype2 | 将静态字段(由index指定)压入栈 |
例如下面代码:
public class TestA {
int x;
int y;
}
public class TestMain {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
TestA testA = new TestA();
testA.x = 3;
testA.y = 4;
}
}
用javap工具查看其字节码指令为:
Compiled from "TestMain.java"
public class TestMain extends java.lang.Object{
public TestMain();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #16; //class TestA 新建 TestA对象
3: dup //
4: invokespecial #18; //Method TestA."<init>":()V 调用构造方法
7: astore_1 //存入位置为1的局部变量
8: aload_1 //取出位置为1的局部变量压入栈
9: iconst_3 //常量3入栈
10: putfield #19; //Field TestA.x:I 赋值
13: aload_1
14: iconst_4
15: putfield #23; //Field TestA.y:I
18: return
}
类型检验操作码:用来检查栈顶的对象引用是指向一个类的实例,还是指向以紧随操作码的操作数作为索引的接口。在这两种情况下,虚拟机都会产生指向常量池入口的无符号16位长度索引,然后把它的值赋给操作码后的两个字节。如果还没有这些内容,虚拟机会解析常量池入口。
如果所给的对象不是指定的类或接口的实例,checkcast指令会抛出一个CheckCastException异常,否则,任何事情都不会发生,对象引用仍在栈中,下一条指令会接着执行。这条指令确保运行时类型转换的安全,并且是java虚拟机安全框架的组成部分。
instanceof指令从栈顶端弹出对象引用,然后压入1或者0。如果对象确实是指定的类或者接口的实例,就像栈中压入1;反之,就向栈中压入0。instanceof指令用来实现java语言中的instanceof关键字。
操作码 | 操作数 | 说明 |
checkcast | indexbyte1,indextype2 | 如果栈中的对象引用不能转化为位于index位置的类,则抛出ClassCastException异常 |
instanceof | indexbyte1,indextype2 | 如果栈中的对象引用是位于index位置的类的实例,则向栈中压入true(1),否则压入false(0) |