Java做爲OOP語言,抽象性不言而喻。若是須要深刻了解Java語言的實現機制,則不得不對Java語言中基礎的概念有清晰的瞭解。今天是我在cnblog上寫博客的第一天,但願今天的博客能夠是我成爲將來"大牛"跨出的第一步。html
面嚮對象語言中,對象概念其實挺抽象的,對於初學者甚至有開發經驗的同志來講都不太容易弄明白。最近看到這篇牛人寫的文章,以爲蠻受益的,和你們共同分享吧。翻譯有些拙劣,"大牛"請忽略我直接看原文,嘻嘻~。java
原文出處連接:http://www.programcreek.com/2011/11/what-do-java-objects-look-like-in-memory/面試
We know functions are implemented in memory as a stack of activation records. And we know Java methods are implemented as a stack of frames in JVM Stack and Java objects are allocated in Heap.編程
函數在內存中經過一堆"活動記錄"(activation record,活動記錄也叫棧幀)實現。咱們也知道Java方法在虛擬機棧中經過一堆"棧幀"(stack frame)實現,Java對象在"堆"(Heap)中分配內存空間。數組
COMMENT1:瀏覽器
1)Java中funtion和procedure統稱爲method。通常來講function和procedure是有區別的(固然不少編程書上是混用的,我也是無語==!)安全
funtion -- 無返回值(void或構造函數那樣返回值類型都沒寫的)的method; procedure -- 有返回值的method數據結構
2)棧幀(stack frame)也稱活動記錄(activation record)。method在被調用(called)時會將方法區的方法壓入棧幀,棧幀中保存有局部變量,返回值類型等信息。具體內容本文不深究。app
How do Java objects look like in heap? Once an object is laid out in memory, it's just a series of bytes.jvm
Java對象在"堆"中到底長得啥樣呢?一旦對象被放到內存中,此時它僅僅就是一系列的字節。
Then how do we know where to look to find a particular field? Keep an internal table inside the compiler containing the offsets of each field.
那麼咱們是如何知道到哪裏去查看、尋找這樣一個特別的字段(field)的呢?在編譯器內部保存有一張包含每個字段偏移量的表。
COMMENT2:
1)field有的譯做"域"、"字段",其實都是一個意思。都是指類的成員變量(包括static成員變量,只不過static存放在方法區,屬於類,全部類對象共享)
2)對象成員變量自動尋址:編譯器內部會保存一張虛擬表(Virtual Table or called Vtable),包括每一個對象成員變量相對於對象空間首地址(第一個成員變量)的地址偏移量,這
樣就可以輕鬆地訪問對象的成員變量了。
Here is an example of an object layout for class "Base"(B). This class does not have any method, how methods are laid out in memory is in the nextsection.
舉個Base類實例對象的例子。這個類沒有任何的方法,它的方法如何在內存中被安排放置將在下一部分中解釋。
If we have another class "Derived"(D) extending this "Base" class. The memory layout would look like the following:
若是咱們有另外一個Derived類繼承了Base類。內存佈局狀況以下:
Child object has the same memory layout as parent objects, except that it needs more space to place the newly added fields. The benefit of this layout is that a pointer of type B pointing at a D object still sees the B object at the beginning. Therefore, operations done on a D object through the B reference guaranteed to be safe, and there is no need to check what B points at dynamically.
子類對象除了須要更多空間去放置新增的字段外,其他的和父類對象有相同類型的內存佈局。這樣佈局的好處就是:當有一個Base類引用指向Derived類對象時,引用仍然能訪問內存區域開始的Base類對象。所以,全部經過Base類引用對Derived類對象上的操做是確保安全的,也就沒有必要動態地去檢查Base類所指向的內容。
COMMENT3:
1) pointer和reference
其實有時我在看Java英文文獻的時候也是傻傻分不清到底在說那個。若是是C++那pointer和reference(引用和指針表面上是兩碼事,引用在底層也是經過指針實現的)明顯就是兩個概念。而在Java引用和C++引用表面上根本就是不一樣的概念,Java中的引用更像C++的指針變量,只不過是閹割版的C++指針變量(Java引用和C++的type * const ptr等價,即ptr的值不能改變,可是ptr指向的內容能夠改變)。
2) 向上造型(也稱"上溯造型")語法現象
The benefit of this layout is that a pointer of type B pointing at a D object still sees the B object at the beginning.
B類(父類)的引用在指向D類對象後,仍然可以訪問B對象。still sees the B object at the beginning 就說明了Object B 和Object D 開頭部份內存是相同的,Object D開頭的內存能夠認爲就是Object B。
經過上述的分析,咱們看下面一道筆試題:
1 import static java.lang.System.out; 2 3 class SupClass { 4 int x = 10; 5 void show() { 6 System.out.println("SupClass"); 7 } 8 } 9 10 class SubClass extends SupClass{ 11 int x = 20; 12 void show() { 13 System.out.println("SubClass"); 14 } 15 } 16 17 public class Test { 18 public static void main(String[] args) { 19 SupClass sup = new SubClass(); 20 out.println(sup.x); 21 } 22 }
SupClass sup = new SubClass(); 父類(SupClass)引用指向子類(SubClass)對象,因爲父類引用只能訪問子類中從父類繼承的成分,所以x = 20是不可以被sup訪問的。
答案就是10,而不是20。
3)爲何父類引用能夠指向子類對象而編譯器不報錯?
Therefore, operations done on a D object through the B reference guaranteed to be safe, and there is no need to check what B points at dynamically.
所以,全部經過Base類引用堆D對象的操做確保是安全的,不須要動態地檢查Base類到底引用了啥。
這句話其實能夠很好地解釋爲何這種操做編譯器不報錯。其實仔細理解起來是這麼回事,因爲子類從父類繼承的那部分結構與父類相同(經過super關鍵字,此時子類對象中多了父類的那部份內容)。父類引用能夠經過子類的對象來訪問子類從父類繼承來的那部分,於是編譯器不會報錯。並且這種訪問形式是安全的,既不能改變父類的內容,也不會去訪問子類多出來的那部分。
4)深刻堆內存
堆內存是什麼?堆內存就是物理機上的一段虛擬內存(Virtual Memory)。哪啥叫虛擬內存?虛擬內存實際是硬盤的東西,只是用部分硬盤的儲存容量來做爲JVM的內存,顯然這個內存是假的,和物理機的內存是兩碼事。固然因爲JVM(Java Virtual Machine)就是一個虛擬計算機,所以不光是堆,其實JVM的全部內存(包括Java棧等)都是物理機中的虛擬內存,即實際硬盤中的儲存空間。
JVM堆內存實際上是鏈表(Linked List)這種數據結構維護的,所以堆內存中的連續內存每每是指邏輯連續,物理堆內存上是不連續。
Following the same logic, the method can be put at the beginning of the objects.
根據相同的邏輯,方法能夠被放在對象的開頭。
However, this approach is not efficient. If a class has many methods(e.g. M), then each object must have O(M) pointers set. In addition, each object needs to have space for O(M) pointers. Those make creating objects slower and objects bigger.
然而,此方法效率並不高。若是一個類有不少的方法,那麼每一個對象必須有一個指針集合(就是指不少指針變量而已)。除此以外,每一個對象須要有空間去儲存這些指針變量。這些會致使建立對象變得更慢,同時對象空間變得更大。
COMMENT4:
1)理論上咱們在建立類對象時將方法寫在對象內存的開頭,可是相比有限的對象成員變量來講,對象的方法可能有不少,若是每次建立一個對象就須要許多指向方法的指針,那麼無疑堆內存將會變得很大,同時在建立對象時會變得很慢(對象建立是從首地址開始一個一個建立的)。
2)對象不包括方法,而是包括了指向方法的指針變量。方法儲存在方法區中。在這種模型(JVM不使用這種模型)中,對象在調用非靜態方法時,堆中的指針變量在須要的時候通過堆中的指針變量訪問方法區中相應的方法,並將其放入Java棧中,對應一個棧幀。
The optimization approach is to create a virtual function table (or vtable) which is an array of pointers to the member function implementations for a particular class. Create a single instance of the vtable for each class and make each object store a pointer to the vtable.
優化方法是建立一個特定類虛函數表,這個虛函數表是指向成員函數實現的指針數組。爲每一個類建立虛表的單一實例,同時讓每一個對象儲存一個指向虛函數表的指針。
COMMENT5:
1)JVM實際使用就是上面第二種模型(方法區在存放方法的同時時,也會存放一份類虛函數表,虛函數表放方法代碼)。
Code for Base.sayHi指的是方法區中的方法代碼信息;sayHi指的是堆中建立的指向方法區方法的方法指針;Vtable*指的是方法指針數組集合,用於建立動態指針數組(這些動態指針指向棧中的具體棧幀),被全部類對象共享(儲存在方法區)。
2)對象能夠訪問堆中常量池內容,說明啥?是否是說明堆中還存在一個指針指向常量池。並且訪問常量池的內容僅限某個類的內容。所以能夠看出,實際上堆中對象還存有.class句柄指針。固然這個是我猜的,沒有啥依據。
3)我想,看到這你我都明白啥是真正意義上的對象了。對象=.class信息句柄指針(能指向方法區常量池)+虛函數表指針變量(指向方法區的方法)+對象成員變量(數據)
4)類的方法儲存在方法區,被全部類的對象所共享。具體對象的虛函數表內的方法指針被建立時,會將方法區中的方法放入Java棧,這個具體的指針和棧幀綁定。這樣每一個類對象經過虛函數表解決了必須調用衆多方法的問題,建立對象速度比第一種模型快,對象所需的堆內存空間也比第一種要小。
經過上面部分討論,能夠解決如下這幾道面試題:
1 public class TestClass { 2 public static void main(String[] args) { 3 SupClass sup = new SubClass(); 4 sup.show1(); 5 sup.show2(); 6 } 7 } 8 9 class SupClass { 10 int x = 10; 11 public void show1() { 12 System.out.println("SupClass"); 13 } 14 public void show2() { 15 System.out.println(x); 16 } 17 } 18 19 class SubClass extends SupClass{ 20 int x = 20; 21 public void show1() { 22 System.out.println("SubClass"); 23 } 24 public void show2() { 25 System.out.println(x); 26 } 27 }
試題的答案是:
SubClass
20
分析:本題考查的是徹底是方法的覆蓋(重寫override)知識點。
1)在同一個類中,若是有兩個方法的方法簽名相同,編譯器會報錯。從計算機思惟來講,機器處理的必須是明確的東西,不能是不肯定量,若是出現不肯定量,機器會報錯,因爲你把它搞暈了,它不知道該選擇那個。
2)在父子類關係這兩個類中,子類方法簽名和父類方法簽名相同,屬於方法覆蓋。那麼爲何父類引用訪問子類覆蓋父類的方法時只能訪問子類重寫版本而不能是父類版本,並且也不會出現問題?其實,在方法區中子類並無包含父類的方法(包括構造方法),而是擁有各自代碼中所寫的方法。使用父類引用指向子類對象時,若是子類覆蓋父類的方法,那麼就會在子類方法區尋找這個方法,找到就將其放入棧中;若是找不到,因爲有extends關鍵字,此時就會經過super指針指向父類方法區,在父類方法區尋找相應的方法。
3)父類哪些東西不能被子類繼承?
Java繼承機制中,超類的方法(包括構造方法)不能被派生類繼承,而是經過調用機制實現。具體的內容會在後續的文章中討論。
1 public class TestClass { 2 public static void main(String[] args) { 3 SupClass sup = new SubClass(); 4 sup.show1(); 5 sup.show2(); 6 } 7 } 8 9 class SupClass { 10 int x = 10; 11 public void show1() { 12 System.out.println("SupClass"); 13 } 14 public void show2() { 15 System.out.println(x); 16 } 17 } 18 19 class SubClass extends SupClass{ 20 int x = 20; 21 public void show1() { 22 System.out.println("SubClass"); 23 } 24 public void show2(int a) { 25 System.out.println(x); 26 } 27 }
試題的答案是:
SubClass
10
分析:本題是上題的變式,SupClass和SubClass都有2個方法,可是在子類中父類show1()被覆蓋,而父類的show2()方法未被覆蓋。在方法區中父類方法是父類方法,子類方法是子類方法,都是實際寫的方法中哪些代碼,子類方法區空間中並無父類的方法。但在具體對象建立時,子類的虛函數表卻擁有指向父類方法區方法的指針。當子類重寫父類方法後,這張表實際上是更新的,也就是說表中父類的方法指針被子類方法指針覆蓋了。
This is the optimized approach.
上面的就是優化後的方法。
References:
1. Stanford Compilers Lectures
2. JVM
學習感悟:
1)若是國內的博客沒有你須要的答案,或者不滿意,最好可以去國外的社區走走看看,固然Java官方文檔是一切的基礎吧。
2)一些專業名詞確實須要積累,好比activation record我一開始沒有翻譯準確,後來是經過瀏覽器查詢才翻譯準確的。
3)不要怕說錯,就像可能我這篇文章有許多錯的。重在和別人交流,在交流中提高本身吧。
4)有些概念可能沒有表述清晰,須要進一步增強本身的語言表述能力。畢竟任何東西,本身懂和讓別人懂事兩碼事,兩種能力。