先有Class還是先有Object?

Java的對象模型中:

  1. 所有的類都是Class類的實例,Object是類,那麼Object也是Class類的一個實例。

  2. 所有的類都最終繼承自Object類,Class是類,那麼Class也繼承自Object。

這就像是先有雞還是先有蛋的問題,請問實際中JVM是怎麼處理的?

此外,可能的話,介紹一下其它面向對象語言是怎麼處理這個問題的。


簡短答案:「雞?蛋」問題通常都是通過一種叫「自舉」(bootstrap)的過程來解決的。

其實「雞蛋問題」的根本矛盾就在於假定了「雞」或「蛋」的其中一個要先進入「完全可用」的狀態。而許多現實中被簡化為「雞蛋問題」的情況實際可以在「混沌」中把「雞」和「蛋」都初始化好,而不存在先後問題;在它們初始化的過程中,兩者都不處於「完全可用」狀態,而完成初始化後它們就同時都進入了可用狀態。

打個比方,番茄炒蛋。並不是要先把番茄完全炒好,然後把雞蛋完全炒好,然後把它們混起來;而是先炒番茄炒到半熟,再炒雞蛋炒到半熟,然後把兩個半熟的部分混在一起同時炒熟。

引用題主的問題:

Java的對象模型中:

  1. 所有的類都是Class類的實例,Object是類,那麼Object也是Class類的一個實例。

  2. 所有的類都最終繼承自Object類,Class是類,那麼Class也繼承自Object。

這個問題中,第1個假設是錯的:java.lang.Object是一個Java類,但並不是java.lang.Class的一個實例。後者只是一個用於描述Java類與介面的、用於支持反射操作的類型。這點上Java跟其它一些更純粹的面向對象語言(例如Python和Ruby)不同。

而第2個假設是對的:java.lang.Class是java.lang.Object的派生類,前者繼承自後者。

雖然第1個假設不對,但「雞蛋問題」仍然存在:在一個已經啟動完畢、可以使用的Java對象系統里,必須要有一個java.lang.Class實例對應java.lang.Object這個類;而java.lang.Class是java.lang.Object的派生類,按「一般思維」前者應該要在後者完成初始化之後才可以初始化…

事實是:這些相互依賴的核心類型完全可以在「混沌」中一口氣都初始化好,然後對象系統的狀態才叫做完成了「bootstrap」,後面就可以按照Java對象系統的一般規則去運行。JVM、JavaScript、Python、Ruby等的運行時都有這樣的bootstrap過程。

在「混沌」(boostrap過程)里,

  • JVM可以為對象系統中最重要的一些核心類型先分配好內存空間,讓它們進入[已分配空間]但[尚未完全初始化]狀態。此時這些對象雖然已經分配了空間,但因為狀態還不完整所以尚不可使用。

  • 然後,通過這些分配好的空間把這些核心類型之間的引用關係串好。到此為止所有動作都由JVM完成,尚未執行任何Java位元組碼。

  • 然後這些核心類型就進入了[完全初始化]狀態,對象系統就可以開始自我運行下去,也就是可以開始執行Java位元組碼來進一步完成Java系統的初始化了。

在HotSpot VM里,有一個叫做「Universe」的C++類用於記錄對象系統的總體狀態。它有這麼兩個有趣的欄位記錄當前是處於bootstrapping階段還是已經完全初始化好:

jdk8u/jdk8u/hotspot: ade5be2b1758 src/share/vm/memory/universe.hpp

static bool is_bootstrapping() { return _bootstrapping; }
static bool is_fully_initialized() { return _fully_initialized; }

然後Universe::genesis()函數會在bootstrap階段中創建核心類型的對象模型:

jdk8u/jdk8u/hotspot: ade5be2b1758 src/share/vm/memory/universe.cpp

(「genesis」是創世紀的意思,多麼形象)

其中會調用SystemDictionary::initialize()來初始化對象系統的核心類型:

jdk8u/jdk8u/hotspot: ade5be2b1758 src/share/vm/classfile/systemDictionary.cpp

其中會進一步跑到SystemDictionary::initialize_preloaded_classes()來創建java.lang.Object、java.lang.Class等核心類型:

jdk8u/jdk8u/hotspot: ade5be2b1758 src/share/vm/classfile/systemDictionary.cpp

這個函數在載入了java.lang.Object、java.lang.Class等核心類型後會調用Universe::fixup_mirrors()來完成前面說的「把引用關係串起來」的動作:

// Fixup mirrors for classes loaded before java.lang.Class.
// These calls iterate over the objects currently in the perm gen
// so calling them at this point is matters (not before when there
// are fewer objects and not later after there are more objects
// in the perm gen.
Universe::initialize_basic_type_mirrors(CHECK);
Universe::fixup_mirrors(CHECK);

jdk8u/jdk8u/hotspot: ade5be2b1758 src/share/vm/memory/universe.cpp

void Universe::fixup_mirrors(TRAPS) {
// Bootstrap problem: all classes gets a mirror (java.lang.Class instance) assigned eagerly,
// but we cannot do that for classes created before java.lang.Class is loaded. Here we simply
// walk over permanent objects created so far (mostly classes) and fixup their mirrors. Note
// that the number of objects allocated at this point is very small.

// ...
}

就是這樣。

=======================================================

Python里的對象系統里也有這種「雞蛋問題」,也是通過一個bootstrap過程來解決。

「雞蛋問題」在於:Python里的所有類型都確實用一個type object表示,而所有類型都是object類的子類。

換句話說,&類是&的子類;而&既是類又是個對象,是&的實例。這個情況就跟題主原本所想像的Java里的情況一樣——雖然Java並非如此。

關於CPython 2.5的對象系統初始化的剖析,可以參考《Python源碼剖析》的第12章。

具體到CPython 2.7.x的代碼,pythonrun.c的Py_InitializeEx()會做Python運行時的初始化,其中會調用object.c的_Py_ReadyTypes()來按照一個列表的順序初始化核心類型的type對象,具體的初始化動作在typeobject.c的PyType_Ready()。

這些核心類型的type對象在CPython里的C層面的類型是PyTypeObject,其結構是確定的;它們的存儲空間通過靜態變數分配,例如&就聲明為在object.h的PyTypeObject PyType_Type,對應的還有&的PyTypeObject PyBaseObject_Type。

所以在進行初始化動作之前它們的存儲空間就已經有著落了,真正做初始化時只要把它們的相互引用串起來就好。

=======================================================

Ruby里的雞蛋問題跟Python比較相似。Ruby里的所有類都是Class類的實例,而Class類是Object類的子類。

以CRuby 2.2.1為例,核心類型的初始化在這裡:class.c的Init_class_hierarchy(),可以看到也是典型的bootstrap過程:先分配空間,再把相互引用關係串起來,然後完成bootstrap開始進入正常的對象系統運作。

void
Init_class_hierarchy(void)
{
/* 給核心類型的Class對象實例分配空間並串上它們的繼承關係 */
rb_cBasicObject = boot_defclass("BasicObject", 0);
rb_cObject = boot_defclass("Object", rb_cBasicObject);
rb_cModule = boot_defclass("Module", rb_cObject);
rb_cClass = boot_defclass("Class", rb_cModule);

rb_const_set(rb_cObject, rb_intern_const("BasicObject"), rb_cBasicObject);

/* 讓上面創建的Class對象實例的類型信息(klass欄位)指向Class對象 */
RBASIC_SET_CLASS(rb_cClass, rb_cClass);
RBASIC_SET_CLASS(rb_cModule, rb_cClass);
RBASIC_SET_CLASS(rb_cObject, rb_cClass);
RBASIC_SET_CLASS(rb_cBasicObject, rb_cClass);
}

然後看調用它的上層函數:

/*!
* Initializes the world of objects and classes.
*
* At first, the function bootstraps the class hierarchy.
* It initializes the most fundamental classes and their metaclasses.
* - c BasicObject
* - c Object
* - c Module
* - c Class
* After the bootstrap step, the class hierarchy becomes as the following
* diagram.
*
* image html boottime-classes.png
*
* Then, the function defines classes, modules and methods as usual.
* ingroup class
*/

/* ... */
void
Init_Object(void)
{
Init_class_hierarchy();

/* ... */
}


JVM裡面只要先把Class和Object搞出來就好了,並沒有任何證據表明所有的class都要用java來定義,也沒有任何證據表明所有的實例都必須用java的代碼使用new這個關鍵字來創建,再說也沒有任何證據表明一個類的metadata跟代表這個類的類型的實例必須被同時創建。

你完全可以先搞出Object的metadata,然後搞出Class的metadata,然後創造出Class的兩個實例(分別是Object和Class),然後把Object的實例的metadata指向Object的matadata,把Class的實例的metadata指向Class的metadata。

problem solved!


object.h:

typedef struct _Object Object;
typedef struct _Class Class;

struct _Object {
Class* klass; // each Object should contain a pointer to its metaclass,
// which also works as a vtable
};

typedef void (*Ctor)(Object* self, ...);
typedef void (*Dtor)(Object* self);

struct _Class {
Object base; // a Class is an Object, so it
// must derive from Object
char* name; // name of the type
Class* baseClass; // the base class
size_t instanceSize; // size of an instance
Ctor instanceCtor; // constructor of the corresponding
// instance type
Dtor instanceDtor; // destructor of the corresponding
// instance type
};

extern Class ObjectType; // the "Object" object
extern Class ClassType; // the "Class" object

Object* New(Class* type, ...); // allocate an object
void Delete(Object* self); // deallocate an object

void Object_ctor(Object* self); // constructor of Object
void Object_dtor(Object* self); // destructor of Object

void Class_ctor(Class* self); // constructor of Class
void Class_dtor(Class* self); // destructor of Class
char* Class_getName(Class* self);

Class* Object_getClass(Object* self); // get the type of an object
Class* Object_getBaseClass(Object* self); // get the type of the base class
// of an object

object.c:

#include &
#include "object.h"

Class ObjectType = {
{ ClassType }, // the "Object" object is a Class
"Object",
ObjectType, // Object"s base is Object
sizeof(Object),
(Ctor)Object_ctor,
(Dtor)Object_dtor,
};

Class ClassType = {
{ ClassType }, // the "Class" object is also a Class!
"Class",
ObjectType, // Class"s base is Object!
sizeof(Class),
(Ctor)Class_ctor,
(Dtor)Class_dtor,
};

void Object_ctor(Object* self)
{}

void Object_dtor(Object* self)
{}

void Class_ctor(Class* self)
{}

void Class_dtor(Class* self)
{}

char* Class_getName(Class* self)
{
return self-&>name;
}

Class* Object_getClass(Object* self)
{
return self-&>klass;
}

Class* Object_getBaseClass(Object* self)
{
return self-&>klass-&>baseClass;
}

Object* New(Class* type, ...)
{
Object* obj = malloc(type-&>instanceSize);
obj-&>klass = type;
type-&>instanceCtor(obj);
return obj;
}

void Delete(Object* self)
{
self-&>klass-&>instanceDtor(self);
free(self);
}

main.c:

#include &
#include "object.h"

// define a type called "MyObject"

typedef struct _MyObject MyObject;
typedef struct _MyObjectClass MyObjectClass;

struct _MyObject {
Object base; // MyObject derives from Object
};

struct _MyObjectClass {
Class base; // MyObjectClass derives from Class
};

MyObjectClass MyObjectType = {
{
{ ClassType }, // the "MyObject" object is a Class
"MyObject",
ObjectType, // MyObject is derived from Object
sizeof(MyObject),
(Ctor)Object_ctor,
(Dtor)Object_dtor,
},
};

int main(void)
{
Object* obj1 = New(ObjectType);
MyObject* obj2 = (MyObject*)New((Class*)MyObjectType);

printf("%s
", Class_getName(Object_getClass(obj1))); // "Object"
printf("%s
", Class_getName(Object_getBaseClass(obj1))); // "Object"
printf("%s
", Class_getName(Object_getClass((Object*)obj2))); // "MyObject"
printf("%s
", Class_getName(Object_getBaseClass((Object*)obj2))); // "Object"
printf("%s
", Class_getName(Object_getClass((Object*)MyObjectType))); // "Class"
printf("%s
", Class_getName(Object_getBaseClass((Object*)MyObjectType))); // "Object"
printf("%s
", Class_getName(Object_getClass((Object*)ObjectType))); // "Class"
printf("%s
", Class_getName(Object_getBaseClass((Object*)ObjectType))); // "Object"
printf("%s
", Class_getName(Object_getClass((Object*)ClassType))); // "Class"
printf("%s
", Class_getName(Object_getBaseClass((Object*)ClassType))); // "Object"

Delete(obj1);
Delete((Object*)obj2);

return 0;
}

Clang 3.6 測試通過


  1. 所有的類都是Class類的實例,Object是類,那麼Object也是Class類的一個實例。

這句話是完全混亂以及不知所云的。當然,我知道你錯在哪兒,我試著給你理一下。

中文說到「類」,對應的英文是class,ok,這是一個前提,不要認為他們是不同的東西。一個class,描述的是一個抽象的數據結構,或者說,一個class,是一個抽象數據結構的定義,記住,這裡說到的class,是一個語法上的概念,並沒有實體跟它對應。

接下來,在JDK中,提供了兩個預定義的class, Object以及Class。按照Java規範,所有的class(注意我這裡對class和Class的大小寫形式的區分使用,class代表的是語法概念,Class代表JDK中提供的Class數據結構)都是Object的子類,所以, Class一定是Object的子類,它用來描述所有的class的meta信息,比如它有多少個欄位啊,有些什麼方法啊,等等這些數據結構信息。

接下來,看實例是什麼?實例是指根據抽象的class定義在運行時聲明的一段內存區間,該內存區間可以按照class的定義進行合法的訪問。所以,Object不是實例,Object是一個class,可以用一個Class的實例來描述。

最後這句話有點繞,回頭看看你的原文,「所有的類都是Class類的實例」, 類就是類,就是class,是一個語法概念,是你自己定義的一個抽象數據結構,跟實例無關,所以,自然的,Object只是一個class,而不是Class的實例。

看下面的代碼

Class cls = Object.class;

Class cls = new Class(new byte[2048]);

Object obj = cls.newInstance();

Object obj = new Object();

* 注意,第二行代碼實際上是不成立的,但從邏輯上比較容易理解,所以我就生造了這樣一個調用。

第一行代碼中,Class是一個class, Object也是一個class,同時,Object.class指向一個Class實例,該實例可以描述Object的數據結構(實際上,就是內存布局)。

第二行代碼,可以用來解釋,那個Object.class又是哪兒來的,實際上,就是從硬碟上讀入的class文件的二進位數據,然後通過new一個Class的實例,並傳入載入的class文件的二進位數據,Class的內部實現會解釋二進位數據並生成相應的內部描述數據。

第三行代碼,Class的newInstance方法總是通過Class的實例才能調用的,因為如上所述,我們通過實例化一個Class的手段來得到一個類的meta描述。

第四行代碼,是我們通常創建實例的手段,從邏輯上,你可以認為,第4行代碼實際上就等同於第二行代碼加上第三行。

最後,在實際的JVM實現中,並不這樣簡單,但你需要首先從邏輯上理解這些概念之間的關係,然後,自己再去深入學習。再強調一遍,我這裡的描述,都只是邏輯上等價的描述,並不是實際情況。


雖然不會java,不過看著概念和OC裡面的實現差不多,最初的那個root-meta class是一個自己生成自己的meta class,其實這已經是一個哲學問題了。我在思考的時候把DNA和一個人想像成類和對象的關係,然後進化鏈就是繼承鏈,最後生成DNA是靠什麼為模版的,想來想去只有soul靈魂來解釋了啊。而生成類的模版,編程語言一般使用另一種語言實現的,對應到靈魂的話這到底是個啥玩意兒,我當時愣是想了一上午還畫了對象圖。畫出來發現竟然能很好的對應上OC的類關係,只有meta-class那一步對不上,這樣也說得通,本來最初那一點怎麼出來就是沒有答案的,所以在語言實現中只能用一些自己生成自己的這種邏輯來解釋,但這種明顯是一個和整體邏輯相矛盾的bug,不過確實有效


先有Object.


真是一個好問題!

我大概講一下我的想法,對不對不知道,只是想和大家交流一下。

應該是先有Object類再有Class類。

在實現Class類的時候,先繼承了Object類,然後編譯成.class的位元組碼,這樣就能被JVM載入了。

虛擬機載入了所有類。這些類在虛擬機里都能被Class類進行描述,Object類也是,它的欄位,方法什麼的也都能被Class類進行描述和表示。而這個表示類的Class實例是由虛擬機裝載類時自動構造的。

so,題主明白了吧,這裡面不是誰先誰後的關係,而是他們壓根不在同一個世界裡。Object在Class類之前,那是在編譯期;Class能表示Object類,那是在JVM中。這個跟雞和蛋之間的關係是兩碼事,要分開在不同的場景里討論。

剛看到這個問題的時候我愣了一下,好像從沒思考過這個問題,不得不說題主的眼光很獨到。


要弄清楚這個問題其實很簡單,先去看看Java Native Interface。JNI中有兩個類型jobject和jclass,假設jvm是c++實現的,那麼可以在C++層面,jclass自然是jobject的子類!jobject是基本的對象存儲,提供了基本的內存空間和操作。jclass也擴展了jobject,提供了JAVA類的名字和虛表等元信息!所謂java的類,在實現上就是一個jclass實例!

所以問題很清晰了,這裡沒有雞生蛋蛋生雞的問題,JVM啟動後先創建一個jclass實例,填充java.lang.Object所需的元信息,於是我們有了java.lang.Object。然後創建另一個jclass實例,填充java.lang.Object和java.lang.Class的元信息,並標記java.lang.Class繼承自java.lang.Object,建立繼承關係。直接了當。(實際的JVM不一定是這麼實現的,只是可以這麼做而已)。

會誤以為有雞生蛋蛋生雞的本質原因是把java.lang.Class和jclass搞混了,或者根本就不知道jclass的存在!現在幾乎所有的語言都是C/C++實現的。對象實例本質上就是一片內存和跟這片綁定的操作。承載類型描述信息的內存空間是jclass而不是java.lang.Class。事實上先創建java.lang.Class,然後再創建java.lang.Object也是可以的!


感覺看了各位的回答,越看越複雜了!自己繞進去了!苦笑。。。。。!


Class 類的實例表示正在運行的 Java 應用程序中的類和介面——JDK 6 Document

並不能說class{}定義的類就是Class類的實例。因為我們不能說一個定義是一個實例,更不能說對定義的片面描述,是一個實例。Class類的實例,僅僅是能描述class{}定義的類的一部分信息而已。

舉個例子,就好像你對著你的iPad照了一張照片,然後你拿著這張照片對別人說:「你看,這是個iPad,他有一個蘋果的Logo,它是銀色金屬制的外殼」。

別人當然能理解你說的是iPad,你想展示的也是iPad,但是你能說這張照片是一個iPad類的實例嗎?不能,我們不能說一張照片就是一個iPad,否則,蘋果專賣店買照片就好了。

所以,定義能說清楚的事情,就不要去扯實現了,這跟JVM暫時還扯不上關係。

針對1,你的說法有誤,改正後如下:

所有類的自己的Class對象,是Class類的一個實例,Object是類,那麼Object.class也是Class類的一個實例。

針對2,你說的是對的:

綜上所述,我沒有發現任何一個類似於「先有雞還是先有蛋」的問題。


感覺先有object,再抽象為class。


class位元組碼載入到jvm,然後在堆內存生成一個class對象。然後對象指向位元組碼,你就可以通過class對象快樂的操作了。


JVM初始化後Object,Class等基礎類得實例會直接、間接引用到數十個(甚至更多)個系統class及相關實例。顯然jvm初始化不是、不能通過系統預置基礎類/對象的方式完成的。

JVM有它自己的規範,理論上,用javascript也可以實現一個jvm運行在nodejs環境里(要實現thread有難度/或無thread版本)。或者可以用java是實現一個jvm讓它跑在另一個jvm上(這個相對比用C++實現jvm還簡單一些)。雖然hotspot jvm是用C++實現的,但還是不要把C++里的任何概念和java混淆了,用其他語言一樣可以實現jvm。java is java,下面講一下我理解的不涉及具體實現的JVM初始化。

如果我們修改Class.java增加一個Field,重新編譯後替換掉rt.jar里的Class.class可不可以呢?我認為是可以的,jvm初始化過後我們會看到所有的Class的實例都會包含那個新增加的欄位。所以,jvm的初始化應該是從載入rt.jar里的class位元組碼開始的。

先不看Object、Class、String等這些系統類,java/JVM規範里,一個普通類的初始化過程大致是這樣的:

通過ClassLoader載入它的位元組碼文件。

根據位元組碼定義的應用關係載入class的super class和interfaces

分配這個class的實例的內存,並初始化它的super class,interfaces的引用。

執行superclass的初始化(如果有superclass).

執行class的類初始化代碼(&),在這個過程中如果引用到了其他類,則需要重複上面過程載入、分配、初始化這個引用類,同時避免class初始化代碼的重入。

完成初始化,返回class實例。

類初始化過程中不可避免的會存在class之間的循環引用,發生循環引用時可能會訪問到初始化未完成的中間狀態的相關實例。初始化邏輯要避免在這種情況下引起的錯誤。

以上過程是一個普通class裝載並初始化的過程,它是從一個ClassLoader的loadClass開始的。實際上JVM的系統類初始化過程基本上也是這樣的,載入過程和ClassLoader載入class執行的是基本相同的邏輯(位元組碼載入、解析、分配、引用、初始化),只不過它不需要一個java的ClassLoader,是從一個Native入口開始載入的(系統class.getClassLoader返回null)。

可以理解為JVM預置了一個Native的ClassLoader它沒有對應的java實例),通過它來載入系統class的。具體的這個Native的ClassLoader入口在哪裡,不同的JVM有不同的實現了。

這種方式叫「自洽」(不確定合不合適),從一個合適的入口,遵循JVM自身的載入class的規範來完成初始化。

針對題主的問題,JVM初始化時第一個裝載的應該是rt.jar里的Object.class,JVM初始化完成後數十個(可能更多)相關的系統class及相關實例會被裝載。但實際上class初始化是包含多個步驟並存在與其他class有循環引用關係的過程。即使Object.class先被解析、分配內存,但它初始化完成時包括Class.class、String.class、ClassLoader.class等其他相關類也已經被裝載了。如果把JVM初始化過程看成一個原子過程,相當於這些相關的class同時被裝載了。


對象是類的實例化

類就是抽象集合


剛剛腦子裡靈光一閃,出現了這個問題,百思不得其解,這裡的回答可以好好看看啦!


看看那麼多找不到object的程序猿我就突然明白了。


先有class,沒有class哪裡object


先有需求


如果是指繼承關係,那必然是先有Object。

如果只是依賴關係,那是互相依賴,不存在絕對先後,不過在.Net也應該是先有object聲明,然後才有class的聲明。


推薦閱讀:

「重載」的正確讀音是什麼?
如何理解js的面相對象?
清華大學的面向對象程序設計的教材是什麼?
Python中的類(class)相比與函數(function)有什麼異同?

TAG:編程語言 | Java | Java虛擬機JVM | 面向對象編程 |