物件怎么創建,這個太熟悉了,new一下(其實還有很多途徑,比如反射、反序列化、clone等,這里拿最簡單的new來講):
Dog dog = new Dog();
我們總是習慣于固定陳述句的執行,卻對于背后的實作程序缺乏認知,而理解這個程序對后面晦澀難懂的反射和代理其實會有很大幫助,所以請務必學好這塊內容,
在看這篇文章之前,啰嗦一句:如果你死記硬背下面所說的流程等于白看,就算現在記住了,一個禮拜后呢,一個月后你又能記得多少,因為物件創建程序這個知識點平常的作業中基本不會涉及到,太底層了,背熟的知識點不經常加以運用容易遺忘,所以我的建議是什么呢,流程做到心里大概有個數,其中涉及到關鍵的知識點記牢就可以了,
JVM記憶體
先簡單說下java虛擬機記憶體模型和存放內容的區別,兩部分:
- 堆疊記憶體 存放基本型別資料和物件的參考變數,資料可以直接拿來訪問,速度比堆快
- 堆記憶體 存放創建的物件和陣列,會由java虛擬機的自動垃圾回收來管理(GC),創建一個物件放入堆內的同時也會在堆疊中創建一個指向該物件堆記憶體中的地址參考變數,下面說的物件就是存在該記憶體中
下面我們就按照物件生成的程序來一一講解參與其中程序的各個概念
首先有這么一個類,后面的初始化基于這個講解:
/**
* @author 煒哥
* @since 2021-04-18 11:01:41
*
* 執行順序:(優先級從高到低,)靜態代碼塊>構造代碼塊>構造方法>普通方法,
* 其中靜態代碼塊只執行一次,構造代碼塊在每次創建物件是都會執行,
*/
public class Dog {
//默認狗狗的最大年齡是16歲
private static int dog_max_age = 16;
//狗狗的名字
private String dog_name;
{
System.out.println("狗狗的構造代碼塊");
}
static {
System.out.println("狗狗的靜態代碼塊");
}
//無參構造器故意沒設
//有參構造器
public Dog(String dog_name) {
this.dog_name = dog_name;
}
public void getDogInfo(){
System.out.println("名字是:"+dog_name + " 年齡:" + dog_max_age);
}
//狗叫
public static void barking(){
System.out.println("汪汪汪~~~");
}
}
JVM生成.class檔案
一個java檔案會在編譯期間被初始化生成.class位元組碼檔案,位元組碼檔案是專門給JVM閱讀的,我們平時吭哧吭哧寫的一行行代碼最終都會被編譯成機器能看懂的陳述句,這個檔案后面會被類加載器加載到記憶體,

類加載器加載.class檔案
《深入理解Java的虛擬機》中大概有這么一句話:在虛擬機遇到一條new的指令時,會去檢查一遍在靜態常量池中能否定位到一個類的符號參考 (就這個類的路徑+名字),并且檢查這個符號參考代表的類是否已被加載、決議和初始化過,如果不是第一次使用,那必須先執行相應的類加載程序,這個程序由類加載器來完成,
類加載字面意思就可以理解成加載class檔案,更準確點的說法就是會把class檔案變成一個二進制流加載到記憶體中,即把類的描述資訊加載到Metaspace,至于類加載器如何找到并把一個class檔案轉成IO流加載到記憶體中,我后面會專門寫一篇關于類加載器的文章,這里就只要理解創建物件中有這么一步就行了,不過這里面有很重要的概念不得不講:Class物件
知識擴展:Class物件
劃重點,這是個非常重要的概念,理解它對于理解后面的反射和代理會有很大的幫助
類加載器 ClassLoader 加載class檔案時,會把類里的一些數值常量、方法、類資訊等加載到記憶體中,稱之為類的元資料,最終目的是為了生成一個Class物件用來描述類,這個物件會被保存在.class檔案里,可能有新手看到這里會比較懵逼,class也有物件?
當然了,Class是個實實在在的類(用來描述類的類,比較拗口),有構造方法( private ,意味著可以生成物件,但不能手動生成,由JVM自動創建Class物件),類加載器會給每個java檔案都創建一個Class物件,用來描述類,我畫個圖:

//以下操作只能由jvm完成,我們手動做不了
Class cls1 = new Class(Dog.class.getClassLoader());
Class cls2 = new Class(Cat.class.getClassLoader());
Class cls3 = new Class(People.class.getClassLoader());

這個Class物件除了描述對應的類之外還有什么作用呢?也可以生成物件,就是java的反射概念(通過Class實體獲取類資訊) 上面說了,Class類是用來描述像People.Class類的類,那么它里面肯定包含了所有能夠描述該class的所有屬性,比如類名、方法、介面等,我們先到Class類原始碼中瞄一眼:

這里面有個方法 newInstance(),即創建物件, 我把源代碼貼出來并簡單決議下:
@CallerSensitive
public T newInstance()
throws InstantiationException, IllegalAccessException
{
if (System.getSecurityManager() != null) {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
}
if (cachedConstructor == null) {
if (this == Class.class) {
throw new IllegalAccessException(
"Can not call newInstance() on the Class for java.lang.Class"
);
}
try {
Class<?>[] empty = {};
//宣告無參構造物件
final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
// Disable accessibility checks on the constructor
// since we have to do the security check here anyway
// (the stack depth is wrong for the Constructor's
// security check to work)
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
c.setAccessible(true);
return null;
}
});
cachedConstructor = c;
} catch (NoSuchMethodException e) {
//如果class中沒有無參構造方法,那么拋InstantiationException錯誤
throw (InstantiationException)
new InstantiationException(getName()).initCause(e);
}
}
Constructor<T> tmpConstructor = cachedConstructor;
// Security check (same as in java.lang.reflect.Constructor)
int modifiers = tmpConstructor.getModifiers();
if (!Reflection.quickCheckMemberAccess(this, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
if (newInstanceCallerCache != caller) {
Reflection.ensureMemberAccess(caller, this, null, modifiers);
newInstanceCallerCache = caller;
}
}
// Run constructor
try {
//最侄訓是呼叫了無參構造器物件的newInstance方法
return tmpConstructor.newInstance((Object[])null);
} catch (InvocationTargetException e) {
Unsafe.getUnsafe().throwException(e.getTargetException());
// Not reached
return null;
}
}
首先搞清楚 newInstance 兩種方法區別:
Class.newInstance() 只能夠呼叫無參的建構式,即默認的建構式,我們在Class原始碼里也看到了其實最侄訓是呼叫了無參構造器物件 Constructor 的 newInstance 方法,舉個栗子:Dog.class 中是沒有無參構造方法,那么會直接拋出 InstantiationException 例外:
//Dog類中只有一個dog_name的有參構造方法
Class c = Class.forName("com.service.ClassAnalysis.Dog");
Dog dog = (Dog) c.newInstance();//直接拋InstantiationException例外

Constructor.newInstance() 可以根據傳入的引數,呼叫任意構造建構式,也可以反射私有構造器(了解就行)
//Dog類中只有一個dog_name的有參構造方法
Constructor cs = Dog.class.getConstructor(String.class);
Dog dog = (Dog) cs.newInstance("小黑");//執行沒有問題
但是這里面的 newInstance跟我們這次要說的 new 方法存在區別,兩者創建物件的方式不同,創建條件也不同:
- 使用
newInstance時必須要保證這類已經加載并且已經建立連接,就是已經被類記載器加載完畢,而 new 不需要 - class物件的
newInstance方法只能用無參構造,上面已經提到了,而 new 不需要 - 前者使用的是類加載機制,是一種方法,后者是創建一個新類,一種關鍵字
這個不能說newInstance 不方便,相反它在反射、工廠設計模式、代理中發揮了重要作用,后續我也會寫下代理和反射,因為理解起來確實有點繞,還有一點需要注意,不管以哪種方式創建物件,對應的Class物件都是同一個
Dog dog1 = new Dog("旺財");
Dog dog2 = new Dog("小黑");
Class c = Class.forName("com.service.classload.Dog");//為了測驗,加了無參構造
Dog dog3 = (Dog) c.newInstance();
System.out.println(dog1.getClass() == dog2.getClass());
System.out.println(dog1.getClass() == dog3.getClass());

連接和初始化
在此階段首先為靜態static變數記憶體中分配存盤空間,設立初始值(還未被初始化)比如:
public static int i = 666;//被類加載器加載到記憶體時會執行,賦予一個初始值
public static Integer ii = new Integer(666);//也被賦值一個初始值
但請注意,實際上i 的初始值是0,不是666,其他基本資料型別比如boolean的初始值就是false,以此類推,如果是參考型別的成員變數 ii 那么初始值就是null,
Dog dog = new Dog("旺財");//在這里打個斷點
執行,首先會執行靜態成員變數初始化,默認值是0:

但有例外,如果加上了 final 修飾詞那么初始值就是設定的值,

接著對已經分配存盤空間的靜態變數真正賦值,比如為上面的dog_max_age 賦值16,還有執行靜態代碼塊,也就是類似下面的代碼:
static {
System.out.println("狗狗的靜態代碼塊");
}
到這為止,類的加載程序才算完成,
創建實體
在加載類完畢后,物件的所需大小根據類資訊就可以確認了,具體創建的步驟如下:
- 先給物件分配記憶體(包括本類和父類的所有實體變數,不包括上面的靜態變數),并設定默認值,如果有參考物件那么會在堆疊記憶體中申請一個空間用來指向的實際物件,
- 執行初始化代碼實體化,先初始化父類再初始化子類,賦予給定值(尊重長輩是java的傳統美德)
- 物件實體化完畢后如果存在參考物件的話還需要把第一步的堆疊物件指向到堆記憶體中的實際物件,這樣一個真正可用的物件才被創建出來,
說了這么多估計很多人都沒概念,懵逼狀態中,其實很簡單,我們只要記住new的創建物件就兩步:初始化和實體化,再給你們搞一張圖:可以簡單理解②③④為初始化⑤實體化(可惡,我這該死的責任感!)

本文不指望你能使勁弄懂java虛擬機底層加載創建物件的程序(其實有些步驟我都懶得講了,因為說出來也都非常理論化,沒多大意思),是想讓你知道物件的誕生程序有哪幾個重要概念參與了,弄懂這些概念比起單單知道物件創建的程序有意義的多:
- 類加載器,可以找找網上的資料,蠻多的,這塊內容做個了解就行
- Class類和Class物件的概念,請重點掌握,不然理解反射和動態代理很費勁,spring的原始碼也會難以理解
- 堆疊記憶體和堆記憶體以及對應的基本型別和參考型別,也很重要,爭取能基本理解
來源:blog.csdn.net/qq_16887951/article/details/115872678
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2022最新版)
2.勁爆!Java 協程要來了,,,
3.Spring Boot 2.x 教程,太全了!
4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!
5.《Java開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/530056.html
標籤:Java
上一篇:JavaaWeb中對request,session,application的理解
下一篇:Python的優點和缺點
