オブジェクトと参照
新しいオブジェクト
最も簡単な例:
new Object();
簡単に言えば、新しいオブジェクト()は、JVMのヒープメモリに割り当てられ、作成されたObject型(インスタンス)のインスタンスであります
一例として、公共の方法に、見て:
PS:かどうかの方法は、パブリックまたはプライベート/保護された/パッケージ方法、または工事の方法、でも静的ブロック、静的変数、インスタンス変数、このアクションのための新しいオブジェクトには、それは非常に同じです
public class Test {
public void fun1() {
Object o = new Object();
}
}
このメソッドが呼び出されたときに、テストクラスのFUN1の方法でオブジェクトをインスタンス化され、Object型の変数に割り当てられ、何が起こったのか?
1.実行javac Test.java
にコンパイルTest.class
ファイル
2.実行はjavap -v Test.class
、コンパイル済み閲覧できます.class
バイトコードファイルを。ここだけのFUN1です
public void fun1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
8: return
LineNumberTable:
line 3: 0
line 4: 8
フォーカスコードセクション
= 2スタックは、プロセスは、オペランドスタックの深さが必要であることを示している2
地元の人たち= 2のプロセスは、スロットの2つのローカル変数のスペースを必要としていることを示し
ここではオフセットと対応するJVM命令セットが続いている、私たちは何かをする命令のステップセットすることにより、これらのステップを分析することができます
まず、オペランドスタック及びローカル変数領域の初期化はようなものです:
命令セット | 対応するコード |
---|---|
0:新しい、java.lang.Object型のインスタンスを作成し、スタックのオペランドスタックにその基準値(PSこれは、基準値を参照しないObject o )オペランドスタックの:[フリー]、[objectrefに】 ローカル変数テーブル:[この]、[無料] |
新しいオブジェクト() |
3:DUP、値スタックオペランドスタックをコピーし、スタックにオペランドの値をスタック オペランドスタック:[objectrefに]、[objectrefに】 ローカル変数テーブル:[本]、[フリー] |
新しいオブジェクト() |
4:invokespecial、java.lang.Objectのを呼び出します」
オペランドスタック:[フリー]、[objectrefに】 ローカル変数テーブル:[本]、[フリー] |
新しいオブジェクト() |
7:ローカル変数にastore_1、スタックの最上位の基準値を第二 オペランドスタック:[フリー]、[遊離] ローカル変数テーブル:[本]、[objectrefに】 |
オブジェクトo =新しいオブジェクト()、主に代入演算子 |
8:現在のメソッドからの戻りは、voidを返します |
上記の手順の解析から求めることができ、新たなオブジェクト操作で簡単な方法、JVMは、すなわち、3つの命令が実行されます。
- オブジェクトを作成し、基準値をスタック
- スタック値のトップをコピーします
- スーパークラスのコンストラクタを呼び出します
より簡単にあいまいさにつながるobjectrefにこの基準値は、我々は通常の参照があると言うObject o = new Object()
の左オペレータ与えるオブジェクトOは、この文は、参照を作成しないことに注意してに、そのオブジェクトのインスタンスへの参照、にローカル変数
VSは、割り当てが割り当てられていません
あなたは、使用するオブジェクトを作成すると、このような状況を見て
Object o = new Object();
o.toString();
Object型のインスタンスを作成し、その呼び出しtoString
方法を
同じ文言も、このようになります
new Object().toString();
これらの2つの方法のいずれかの違いはありますか?見た目にJVMの命令で
出典:
public class Test {
public void invokeWithoutReference() {
new Object().toString();
}
public void invokeWithReference() {
Object o = new Object();
o.toString();
}
}
(命令セットjavap -v Test.class
のみ命令セット保持部):
public void invokeWithoutReference();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>": ()V
7: invokevirtual #3 // Method java/lang/Object.toString:()Ljava/lang/String;
10: pop
11: return
public void invokeWithReference();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>": ()V
7: astore_1
8: aload_1
9: invokevirtual #3 // Method java/lang/Object.toString:()Ljava/lang/String;
12: pop
13: return
そこ基準との間の差はありませんとで、基準となるinvokeWithReference
発生Object
例、実行されるastore_1
とaload_1
、前記2つの命令。
astore_1
代入演算子を表すスタック基準値が第2のローカル変数テーブルのスロットに格納されているショーは、( =
)を行います
aload_1
第二の参照型のローカル変数スタックはオペランドスタックにプッシュ前記
具体的には、命令実行セットの二つの異なる方法で:
invokeWithoutReference
命令セット | 対応するコード |
---|---|
0:新しい、オペランドスタック上java.lang.Object型、スタック及びその基準値のインスタンスを作成する :オペランドスタック[遊離]、[objectrefに】 ローカル変数テーブル:[これ] |
新しいオブジェクト() |
3:DUP、値スタックオペランドスタックをコピーし、スタックにオペランドの値をスタック オペランドスタック:[objectrefに]、[objectrefに】 ローカル変数テーブル:[本] |
新しいオブジェクト() |
4:invokespecial、java.lang.Objectのを呼び出します」
オペランドスタック:[フリー]、[objectrefに】 ローカル変数テーブル:[本] |
新しいオブジェクト() |
7:INVOKEVIRTUAL、toStringメソッドは、値を返すためのtoString java.lang.Objectのメソッド呼び出し、スタックが押し込まれる場合、結果が実行される オペランドスタック:[フリー]、[java.lang.Stringで] ローカル変数テーブル:[この] |
新しいオブジェクト().toString(); |
10:ポップ、スタック値ポップのトップ スタックの操作:[フリー]、[遊離] ローカル変数テーブル:[本] |
新しいオブジェクト().toString(); |
11:リターンは、現在のメソッドからvoidを返します |
invokeWithReference
命令セット | 対応するコード |
---|---|
0:新規のスタックオペランドスタックの最上位にjava.lang.Object型、及びその基準値のインスタンスを作成する :オペランドスタック[遊離]、[objectrefに】 ローカル変数テーブル:[本]、[アイドル] |
新しいオブジェクト() |
3:DUP、値スタックオペランドスタックをコピーし、スタックにオペランドの値をスタック オペランドスタック:[objectrefに]、[objectrefに】 ローカル変数テーブル:[本]、[フリー] |
新しいオブジェクト() |
4:invokespecial、java.lang.Objectのを呼び出します」
オペランドスタック:[フリー]、[objectrefに】 ローカル変数テーブル:[本]、[フリー] |
新しいオブジェクト() |
7:ローカル変数にastore_1、スタックの最上位の基準値を第二 オペランドスタック:[フリー]、[遊離] ローカル変数テーブル:[本]、[objectrefに】 |
O =オブジェクト新しいオブジェクト(); |
8: aload_1,将第二个本地变量推入栈顶 操作数栈:[空闲], [objectref] 局部变量表:[this], [objectref] |
|
9: invokevirtual,调用java.lang.Object的toString方法,因为toString方法有返回值,所以这里会将执行的结果推入栈顶 操作数栈:[空闲], [java.lang.String] 局部变量表:[this], [objectref] |
new Object().toString(); |
12: pop,将栈顶数值弹出 操作数栈:[空闲], [空闲] 局部变量表:[this], [objectref] |
new Object().toString(); |
13: return,从当前方法返回void |
引用?
首先,什么是引用?
《深入理解JVM虚拟机》一书中多次对Java的引用进行了讨论
对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)——《深入理解JVM虚拟机》 2.2.2 Java虚拟机栈
建立对象是为了使用对象,我们的Java程序需要通过栈上的reference数据来操作堆上的具体对象。——《深入理解JVM虚拟机》 2.3.3 对象的访问定位
一般来说,虚拟机实现至少都应当能通过这个引用做到两点,一是从此引用中直接或间接地查找到对象在Java堆中地数据存放地起始地址索引,二是此引用中直接或间接地查找到对象所属数据类型在方法区中的存储的类型信息 ——《深入理解JVM虚拟机》 8.2.1 局部变量表
对于new Object()
来说,JVM执行的new(0xbb)
指令天然的就会将新实例的引用压入操作数栈的栈顶
而Object o = new Object()
只是利用=
运算符,让JVM执行了astore_n
指令,将这个引用保存到了局部变量表中,以便我们以后可以直接通过o.xxx()
来对这个实例做一些操作
等到我们需要使用的时候,JVM再通过aload_n
将指定的局部变量表中的引用类型值推到操作数栈的栈顶进行后续操作
所以在我看来,Object o
其实是一个引用类型的本地变量
创建对象到底赋值吗?
回到初衷,是否定义一个引用类型的本地变量,没有一个绝对的优劣
Object o = new Object()
仅仅是比new Object()
多在局部变量表中保存了一个Object o
引用类型,但它可以让我们在创建了实例之后,重复对这个实例进行操作
new Object()
在进行了new Object().toString()
这种方式的调用之后,由于局部变量表中没有了该实例的引用,操作数栈中的那个两个由dup
产生的两个引用,也已经分别因为invokespecial
和invokevirtual
弹出栈了,所以这个对象已经没有指向它的引用了
如果我们对于实例只是一次性调用,那么直接new Object()
的方式也未尝不可