ringa_lee
先明確幾個概念,java程式碼是跑在jvm中的,而jvm的記憶體區域被劃分為這麼幾個模組:
程式計數器(Program Counter Register):程式計數器是一個比較小的記憶體區域,用來指示目前執行緒所執行的字節碼執行到了第幾行,可以理解為是目前執行緒的行號指示器。字節碼解釋器在運作時,會透過改變這個計數器的值來取下一條語句指令。
虛擬機棧(JVM Stack):一個執行緒的每個方法在執行的同時,都會建立一個堆疊幀(Statck Frame),在堆疊幀中儲存的有局部變數表、操作站、動態連結、方法出口等,當方法被呼叫時,棧幀在JVM棧中入棧,當方法執行完成時,棧幀出棧。
本地方法棧(Native Method Statck):本地方法棧在作用,運行機制,異常類型等方面都與虛擬機棧相同,唯一的區別是:虛擬機棧是執行Java方法的,而本地方法堆疊是用來執行native方法的,在許多虛擬機器中(如Sun的JDK預設的HotSpot虛擬機器),會將本地方法堆疊與虛擬機器堆疊一起使用。
堆區(Heap):堆區是理解Java GC機制最重要的區域,沒有之一。在JVM所管理的記憶體中,堆區是最大的一塊,堆區也是Java GC機制所管理的主要記憶體區域,堆區由所有執行緒共享,在虛擬機器啟動時建立。堆區的存在是為了儲存物件實例,原則上講,所有的物件都在堆區上分配記憶體(不過現代技術裡,也不是這麼絕對的,也有棧上直接分配的)。
方法區(Method Area):(也稱為永久代),方法區是各個執行緒共享的區域,用於儲存已經被虛擬機載入的類別資訊(即載入類別時需要載入的信息,包括版本、field、方法、介面等資訊)、final常數、靜態變數、編譯器即時編譯的程式碼等。
直接內存(Direct Memory):直接內存並不是JVM管理的內存,可以這樣理解,直接內存,就是JVM以外的機器內存,比如,你有4G的內存,JVM佔用了1G,則其餘的3G就是直接內存,JDK中有一種基於通道(Channel)和緩衝區(Buffer)的內存分配方式,將由C語言實現的native函數庫分配在直接內存中,用存儲在JVM堆中的DirectByteBuffer來引用。由於直接記憶體收到本機器記憶體的限制,所以也可能出現OutOfMemoryError的異常。
明白這幾個基本概念以後再來看看題主疑惑的地方。其實題主疑惑的是在java中,物件的引用是如何實現的。為什麼可以在定義一個類別的同時,定義自己的引用,同時如果再實例化了這個引用以後,難道不會導致無線循環引用下去嗎?
別急我們先來分析下java中一個引用是怎麼實現的:
一個Java的引用存取涉及到3個記憶體區域:JVM棧,堆,方法區。
以最簡單的本地變數引用:Object obj = new Object()為例:
Object obj表示一個本地引用,儲存在JVM棧的本地變數表中,表示一個reference類型資料;
new Object()作為實例物件資料儲存在堆中;
堆中也記錄了Object類別的類型資訊(介面、方法、field、物件類型等)的位址,這些位址所執行的資料儲存在方法區中;
具體的實作方式有很多種,句柄是其中一種,關係如圖所示。
看到這裡應該就明白了。類別本身的信息,類別實例數據,以及指向物件的引用資訊分別放在 java 的方法區和堆疊區以及堆疊區。
在題主的例子中,java載入順序是這樣的:
jvm先載入了方法區的類別定義(但此時並沒有實例化這個類別)
因為 public static final Direction FRONT = new Direction(); 是個靜態變量,所以這個變數也會在 jvm 第一次讀取方法區定義時被裝載進方法區中。
public static final Direction FRONT = new Direction();
同時,這也意味著,在裝載這個變數的同時,也在堆區實例化了這個類別的實例。
注意這裡面的關鍵點,因為 FRONT 變數是靜態變量,而載入類別定義只會載入一次,所以這個靜態變數也只可能載入一次。並不會像非靜態變數一樣因為循環引用重複實例化而導致堆疊溢位。
推薦你看看R大的回答
先有Class還是先有Object? https://www.zhihu.com/questio...
說說你的理解,為什麼類別裡面不能創造自己的物件? 這幾個變數加上了static後就變成了類別的屬性了,只會創建一次。
如果自己都不能創建自己,那其他類別就更不能了。這樣的話這個類別怎麼實例化…
設計模式:單例模式
本質是對java的物件導向程式設計的不理解。看看23種設計模式你可能就會理解
建構函式也是一個方法。
具有 private 存取權的方法表示私有的,只有本類可見。
private
所以,本類別可以呼叫具有 private 存取權的建構子實例化一個物件。
使用內部類別的原因:每個內部類別都能獨立的繼承自一個(介面的)實現,所以無論外部類別是否已經繼承了某個(介面的)的實現,對內部類別都沒有影響。實際上內部類別有效的實現了“多重繼承”,就是說,內部類別允許繼承多個非介面類型。
我們知道內部類別自動擁有對外部類別所有成員的存取權,那麼這是如何做到的嗎?當某個外部類別物件建立了一個內部類別物件時,此內部類別物件必定會秘密的擷取一個指向那個外部類別物件的參考。然後,當你存取外部類別的成員時,就是用那個引用來選擇外部類別的成員。當然這些細節是編譯器處理,這裡的內部類別是非static的。 如果一個類別都無法建立自己的類別對象,那我要你這個類別何用?啊,哈哈哈哈,開玩笑咯
先明確幾個概念,java程式碼是跑在jvm中的,而jvm的記憶體區域被劃分為這麼幾個模組:
程式計數器(Program Counter Register):程式計數器是一個比較小的記憶體區域,用來指示目前執行緒所執行的字節碼執行到了第幾行,可以理解為是目前執行緒的行號指示器。字節碼解釋器在運作時,會透過改變這個計數器的值來取下一條語句指令。
虛擬機棧(JVM Stack):一個執行緒的每個方法在執行的同時,都會建立一個堆疊幀(Statck Frame),在堆疊幀中儲存的有局部變數表、操作站、動態連結、方法出口等,當方法被呼叫時,棧幀在JVM棧中入棧,當方法執行完成時,棧幀出棧。
本地方法棧(Native Method Statck):本地方法棧在作用,運行機制,異常類型等方面都與虛擬機棧相同,唯一的區別是:虛擬機棧是執行Java方法的,而本地方法堆疊是用來執行native方法的,在許多虛擬機器中(如Sun的JDK預設的HotSpot虛擬機器),會將本地方法堆疊與虛擬機器堆疊一起使用。
堆區(Heap):堆區是理解Java GC機制最重要的區域,沒有之一。在JVM所管理的記憶體中,堆區是最大的一塊,堆區也是Java GC機制所管理的主要記憶體區域,堆區由所有執行緒共享,在虛擬機器啟動時建立。堆區的存在是為了儲存物件實例,原則上講,所有的物件都在堆區上分配記憶體(不過現代技術裡,也不是這麼絕對的,也有棧上直接分配的)。
方法區(Method Area):(也稱為永久代),方法區是各個執行緒共享的區域,用於儲存已經被虛擬機載入的類別資訊(即載入類別時需要載入的信息,包括版本、field、方法、介面等資訊)、final常數、靜態變數、編譯器即時編譯的程式碼等。
直接內存(Direct Memory):直接內存並不是JVM管理的內存,可以這樣理解,直接內存,就是JVM以外的機器內存,比如,你有4G的內存,JVM佔用了1G,則其餘的3G就是直接內存,JDK中有一種基於通道(Channel)和緩衝區(Buffer)的內存分配方式,將由C語言實現的native函數庫分配在直接內存中,用存儲在JVM堆中的DirectByteBuffer來引用。由於直接記憶體收到本機器記憶體的限制,所以也可能出現OutOfMemoryError的異常。
明白這幾個基本概念以後再來看看題主疑惑的地方。其實題主疑惑的是在java中,物件的引用是如何實現的。為什麼可以在定義一個類別的同時,定義自己的引用,同時如果再實例化了這個引用以後,難道不會導致無線循環引用下去嗎?
別急我們先來分析下java中一個引用是怎麼實現的:
一個Java的引用存取涉及到3個記憶體區域:JVM棧,堆,方法區。
以最簡單的本地變數引用:Object obj = new Object()為例:
Object obj表示一個本地引用,儲存在JVM棧的本地變數表中,表示一個reference類型資料;
new Object()作為實例物件資料儲存在堆中;
堆中也記錄了Object類別的類型資訊(介面、方法、field、物件類型等)的位址,這些位址所執行的資料儲存在方法區中;
具體的實作方式有很多種,句柄是其中一種,關係如圖所示。
看到這裡應該就明白了。類別本身的信息,類別實例數據,以及指向物件的引用資訊分別放在 java 的方法區和堆疊區以及堆疊區。
在題主的例子中,java載入順序是這樣的:
jvm先載入了方法區的類別定義(但此時並沒有實例化這個類別)
因為
public static final Direction FRONT = new Direction();
是個靜態變量,所以這個變數也會在 jvm 第一次讀取方法區定義時被裝載進方法區中。同時,這也意味著,在裝載這個變數的同時,也在堆區實例化了這個類別的實例。
注意這裡面的關鍵點,因為 FRONT 變數是靜態變量,而載入類別定義只會載入一次,所以這個靜態變數也只可能載入一次。並不會像非靜態變數一樣因為循環引用重複實例化而導致堆疊溢位。
推薦你看看R大的回答
說說你的理解,為什麼類別裡面不能創造自己的物件?
這幾個變數加上了static後就變成了類別的屬性了,只會創建一次。
如果自己都不能創建自己,那其他類別就更不能了。這樣的話這個類別怎麼實例化…
設計模式:單例模式
本質是對java的物件導向程式設計的不理解。看看23種設計模式你可能就會理解
建構函式也是一個方法。
具有
private
存取權的方法表示私有的,只有本類可見。所以,本類別可以呼叫具有
private
存取權的建構子實例化一個物件。使用內部類別的原因:每個內部類別都能獨立的繼承自一個(介面的)實現,所以無論外部類別是否已經繼承了某個(介面的)的實現,對內部類別都沒有影響。實際上內部類別有效的實現了“多重繼承”,就是說,內部類別允許繼承多個非介面類型。
我們知道內部類別自動擁有對外部類別所有成員的存取權,那麼這是如何做到的嗎?當某個外部類別物件建立了一個內部類別物件時,此內部類別物件必定會秘密的擷取一個指向那個外部類別物件的參考。然後,當你存取外部類別的成員時,就是用那個引用來選擇外部類別的成員。當然這些細節是編譯器處理,這裡的內部類別是非static的。
如果一個類別都無法建立自己的類別對象,那我要你這個類別何用?啊,哈哈哈哈,開玩笑咯