對象的創建
對象創建過程(“微觀”)
- 當使用構造器創建對象或靜態方法、非常數靜態域首次被訪問時,Java解釋器必須查找類路徑,定位.class文件
- 加載.class文件,創建Class對象,有關靜態初始化的所有動作都會執行(靜態初始化)
- 當使用new關鍵字創建對象時,在堆上分配足夠的存儲空間
- 存儲空間清零/null
- 執行所有出現於字段定義處的初始化動作(非靜態初始化)
- 執行構造器
對象的創建過程(“宏觀”)
- 基類靜態(static)字段初始化
- 本類靜態(static)字段初始化
- 基類非靜態字段初始化
- 本類非靜態字段初始化
- 執行基類構造器
- 執行本類構造器
列:
public class Test{
public static void main(String[] args){
B b=new B();
}
}
class A{
static int ai=10;
static{
System.out.println("ai="+ai);
}
public A(){
System.out.println("A");
}
}
class B extends A{
static int bi=100;
static{
System.out.println("bi="+bi);
}
public B(){
System.out.println("B");
}
}
根據對象的創建過程來看,創建B類的對象過程:
- 第一步:基類靜態字段ai初始化,且輸出ai=10;
- 第二步:本類靜態字段bi初始化,且輸出bi=100;
- 第三步:執行基類構造器,輸出A;
- 第四步:執行本類構造器,輸出B;
故程序輸出:
ai=10
bi=100
A
B
對象的存儲
Java採用了動態內存分配方式。每當想要創建新對象時,就要使用new關鍵字來構建此對象的動態實例。
很多時候我們都認爲通過new關鍵字創建的對象都是被分配在堆上,包括Java編程思想第四版P14也有說到“只能以一種方式創建對象(在堆上創建)”,但這種說法是有一定問題的,Java中new出來的對象並不一定分配在堆上,主要受逃逸分析和TLAB(Thread Local Allocation Buffer)影響,對象可能會被分配到棧上。
逃逸分析:這裏不說逃逸分析是什麼,只需要知道它是一種算法,目的在於分析出Java對象引用的使用範圍,然後由編譯器利用逃逸分析的結果,對程序進行優化。Java在Java SE 6u23及以後的版本中默認開啓了逃逸分析。經過逃逸分析可以得到三種對象的逃逸狀態:
第一種:方法逃逸,對象引用逃出了方法,分配到堆內存上
第二種:線程逃逸,如類變量或實例變量,可能被其他線程訪問到,分配到堆內存上
第三種:NoEscape(無逃逸),同步消除、棧上分配、標量替換。如:可以將這個對象分配到棧內存上
舉個例子:
public class Test{
public static A a2;
//方法逃逸
public A run1(){
A a1=new A();
return a1;
}
//線程逃逸
public void run2(A a2){
this.a2=a2;
}
//無逃逸
public void run3(){
A a3=new A();
}
}
class A{}
程序中:
對象引用a1在方法run1中被定義並返回,此爲方法逃逸,該對象分配到堆
類變量a2在方法run2被賦值,因爲該類變量可被其他線程訪問,此爲線程逃逸,該對象分配到堆
對象引用a3在方法run3中被定義,且爲無逃逸,該對象可以被分配到棧
TLAB:TLAB是JVM在內存新生代(Eden Space)中開闢的一小塊線程私有區域,在Java程序中有很多用過即丟的小對象,它們不存在線程共享也適合被快速GC,所以對於這些小對象通常JVM會優先分配到TLAB上
Java中每個線程都有一個自己的緩衝區TLAB,每個TLAB只有一個線程可以操作
強調
-
編譯器強制要求初始化基類,並要求在構造器起始處就要這麼做,這也是創建對象時先執行基類構造器,再執行本類構造器的原因。
- 棧上分配:在一般應用中,不會逃逸的局部對象佔比很大,如果使用棧上分配,那大量對象會隨着方法結束而自動銷燬,垃圾回收系統壓力就小很多。
- 同步消除:線程同步本身比較耗時,如果確定一個變量不會逃逸出線程,無法被其它線程訪問到,那這個變量的讀寫就不會存在競爭,對這個變量的同步措施可以清除。
- 標量替換:標量就是不可分割的量,java中基本數據類型,reference類型都是標量。相對的一個數據可以繼續分解,它就是聚合量。把一個對象拆散,將其成員變量恢復到基本類型來訪問就叫做變量替換。如果逃逸分析證明一個對象不會被外部訪問,並且這個對象可以被拆散的話,程序真正執行的時候將可能不創建這個對象,而是直接在棧上創建若干個成員變量。