先有Class还是先有Object_-RednaxelaFX
简短答案:“鸡・蛋”问题通常都是通过一种叫“自举”(bootstrap )的过程来解决的。
其实“鸡蛋问题”的根本矛盾就在于假定了“鸡”或“蛋”的其中一个要先进入“完全可用”的状态。而许多现实中被简化为“鸡蛋问题”的情况实际可以在“混沌”中把“鸡”和“蛋”都初始化好,而不存在先后问题;在它们初始化的过程中,两者都不处于“完全可用”状态,而完成初始化后它们就同时都进入了可用状态。
打个比方,番茄炒蛋。并不是要先把番茄完全炒好,然后把鸡蛋完全炒好,然后把它们混起来;而是先炒番茄炒到半熟,再炒鸡蛋炒到半熟,然后把两个半熟的部分混在一起同时炒熟。
引用题主的问题:
Java的对象模型中:
- 所有的类都是Class类的实例,Object是类,那么Object也是Class类的一个实例。
- 所有的类都最终继承自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类的子类。
换句话说,<type ‘type’>类是<type ‘object’>的子类;而<type ‘object’>既是类又是个对象,是<type ‘type’>的实例。这个情况就跟题主原本所想像的Java里的情况一样——虽然Java并非如此。
关于CPython 2.5的对象系统初始化的剖析,可以参考
的第12章。
具体到CPython 2.7.x的代码,
会做Python运行时的初始化,其中会调用
来按照一个列表的顺序初始化核心类型的type对象,具体的初始化动作在
。
这些核心类型的type对象在CPython里的C层面的类型是PyTypeObject,其结构是确定的;它们的存储空间通过静态变量分配,例如<type ‘type’>就声明为在
object.h的PyTypeObject PyType_Type
,对应的还有<type ‘object’>的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
*/
/* ... */
Init_Object(void)
Init_class_hierarchy();
评论区
刘子昱: 我记得以前有个很装逼的人面试我,问我OBJECT CLASS先有鸡的问题,当时其实我没研究过,不过我觉得按照编程的尿性,应该是OBJECT和CLASS一起初始化就可以了。这个人其实自己没研究过,听到我的回答以后很惊讶,连说不可能。。。 👍🏽31 💭N/A IP 🕐2015-05-12 17:37:51
│ └── 魂魄妖梦: 自己都不知道的东西还面试问别人,坑爹呢 👍🏽22 💭N/A IP 🕐2015-12-18 11:06:28
JasonMing: 应该这样说,鸡和蛋在vm层面都是一个c的struct。有些这个维度解释不了的事情,降个维就解决了[飙泪笑] 👍🏽9 💭N/A IP 🕐2021-06-29 18:32:33
│ └── 知乎用户99dUAc: 加一个中间层解决[飙泪笑] 👍🏽1 💭N/A IP 🕐2022-06-21 17:44:05
知乎用户mDJm3Y: 很有道理。语言层面的东西真的还是水面上的冰山一角,更大的部分则隐藏在水下,就是虚拟机。因为虚拟机实现不需要遵循语言层面上的那些规则,它要做的就是让世界诞生,然后世界才有序运行。 👍🏽6 💭N/A IP 🕐2016-08-02 21:18:43
龙帅: 神仙打架。就记住了Object和Class是一起初始化的… 👍🏽4 💭N/A IP 🕐2019-12-23 15:14:40
田永智: 不想讨论 java 的问题,只想说你家番茄炒蛋是先炒番茄啊?我以及我见到的都是先炒鸡蛋来的。 👍🏽3 💭N/A IP 🕐2021-09-11 09:37:12
│ └── tearshark: 明显并不自己做饭的主 👍🏽0 💭N/A IP 🕐2021-12-28 07:42:17
│ └── 胖哥哥: 先炒蛋。可以让高油温把蛋爆香。 👍🏽1 💭N/A IP 🕐2022-07-04 21:14:11
张裕坤: 那是不是说,Object里有一个成员变量指向Class类实例c,c保存这个Object成员、方法的名字和地址的Map映射用作反射? 👍🏽2 💭N/A IP 🕐2015-05-12 16:30:51
│ └── RednaxelaFX: 嗯,可以这样理解。实际到每个JVM里的实现都略不同,但这种理解方式都大致可以套上。HotSpot VM(以JDK8为例),所有class在VM内都用InstanceKlass来记录JVM实际需要的各种信息(包括反射信息),而每个InstanceKlass对象都有一个_java_mirror字段指向该类对应的java.lang.Class实例。java.lang.Class只是一层皮。所以代表java.lang.Object类的InstanceKlass对象就通过_java_mirror字段可以找到对应的java.lang.Class实例。 👍🏽11 💭N/A IP 🕐2015-05-13 14:44:57
│ └── 知乎用户mDJm3Y: 在Maxine里面,kclassinstance是不是就相当于classActor, 👍🏽0 💭N/A IP 🕐2016-08-02 21:20:37
zanxas: 收膝盖的日常。 👍🏽25 💭N/A IP 🕐2015-05-12 14:27:16
manxisuo: 我发现要想把java学得更好,果然要学好cpp啊。 👍🏽9 💭N/A IP 🕐2015-05-12 16:08:15
winter: 自举好像很棒啊,那么,是否有“不举”的手段呢? 👍🏽8 💭N/A IP 🕐2015-05-12 16:37:01
│ └── 青桔柠檬茶: 冬冬又抖机灵[思考] 👍🏽0 💭N/A IP 🕐2021-09-17 23:48:43
慕然: 请问了解到这种深度的话,需要怎样的途径呢?直接看源码,还是有阅读规范还是有较好的书籍呢? 👍🏽2 💭N/A IP 🕐2016-08-11 01:05:03
│ └── AI布道师Warren: 这是人家的工作啊 开发虚拟机的 那肯定熟悉呀 如果是兴趣 可以看看openjdk源码 如果做业务开发 没必要的 👍🏽0 💭N/A IP 🕐2021-10-20 14:12:34
XX XX: 为什么每次讲的都看不懂 👍🏽1 💭N/A IP 🕐2015-05-12 18:03:02
刘立: 茅塞顿开 👍🏽1 💭N/A IP 🕐2015-05-12 14:37:10
沈忱: 所以说从某个角度来看,java的类只是在类型层面有树形关系,以Object为根;而py的类在类型和实例层面上都有树形关系,前者以object为根,后者以type为根,并且这两个根还是互相依赖的。不知道这么理解对么? 👍🏽1 💭N/A IP 🕐2016-07-25 23:53:39
touchmii: 涉及到这种先有鸡还是先有蛋的问题,一般都是更加底层的语言来处理的,java中是jvm的cpp,就像c语言也会依赖汇编初始化运行环境(嵌入式)。 👍🏽0 💭N/A IP 🕐2022-11-08 22:50:38
tarv: 实在的干货。 👍🏽0 💭N/A IP 🕐2015-09-16 18:40:55
taolei0628: 在java的概念里,Object也是Class类的一个实例是对的。但不能等同于C的Instance和class的概念。虽然JVM是C实现的,但不能用C++的逻辑去解释和理解java的概念。 👍🏽0 💭N/A IP 🕐2015-09-16 15:31:51
│ └── 张宝辉: Object 是Class的实例吗? 👍🏽0 💭N/A IP 🕐2017-05-08 19:54:01
│ └── taolei0628: 不是,但应以你们老师的标准答案为准。 👍🏽0 💭N/A IP 🕐2017-06-13 13:19:42
│ └── 张宝辉: 没有老师怎么办? 👍🏽0 💭N/A IP 🕐2017-06-14 07:21:29
知乎用户0cuAhz: 关注你之后发现, 关注你实在太值了… 👍🏽0 💭N/A IP 🕐2015-08-27 18:42:55
罗衣: 牛人,收下膝盖 👍🏽0 💭N/A IP 🕐2015-06-05 16:15:38
花飞蝶舞剑: 每读一遍都有新的收获。 👍🏽0 💭N/A IP 🕐2015-06-04 11:32:04
朱丽啦: 你这么吊你妈妈知道吗 👍🏽0 💭N/A IP 🕐2015-05-23 15:04:18
付新华: 感谢 👍🏽0 💭N/A IP 🕐2015-05-15 06:56:31
BigBigWolf: 受教,感谢。 👍🏽0 💭N/A IP 🕐2015-05-14 22:42:37
liuyangc3: 良心答案 👍🏽0 💭N/A IP 🕐2015-05-14 08:07:33
zenix: R大总是有干货 👍🏽0 💭N/A IP 🕐2015-05-13 19:16:34
Vesine: 请收下我的膝盖 = = 👍🏽0 💭N/A IP 🕐2015-05-13 11:37:51
Kelvin: JavaScript呢? 👍🏽0 💭N/A IP 🕐2015-05-13 11:27:38
│ └── RednaxelaFX: 有空再更新点JS的 👍🏽0 💭N/A IP 🕐2015-05-13 12:15:02
you conquer: 日常涨姿势 👍🏽0 💭N/A IP 🕐2015-05-13 10:22:21
eaxu: 只看懂“简单回答…就是这样” 👍🏽0 💭N/A IP 🕐2015-05-12 23:36:39
李寻欢: 好赞。可惜只能赞一次。一万次才能表达我的敬佩之情。 👍🏽0 💭N/A IP 🕐2015-05-12 21:11:37
仇伟佳: 好详细,赞 👍🏽0 💭N/A IP 🕐2015-05-12 16:45:21
貘吃馍香: 虽然看不懂,但感觉真的很有道理 👍🏽0 💭N/A IP 🕐2015-05-12 16:37:31
peace shi: NB。跪。 👍🏽0 💭N/A IP 🕐2015-05-12 16:17:40
孙权: 请务必先收下我的膝盖 👍🏽0 💭N/A IP 🕐2015-05-12 15:31:26
刘巡: 已关注 👍🏽0 💭N/A IP 🕐2015-05-12 14:54:03
夏大雨: 第一关注就被你刷屏了。 👍🏽0 💭N/A IP 🕐2015-05-12 14:50:12
DreamPiggy: 日常一学 👍🏽0 💭N/A IP 🕐2015-05-12 14:48:16
姜涛: 讲的太好了 👍🏽0 💭N/A IP 🕐2015-05-12 14:37:48
郝熊升: Java和Python、Ruby不一样。class和object之间没有实例关系。想问一下,那这样有什么功能是Python能做到的而Java做不到? 👍🏽0 💭N/A IP 🕐2019-07-18 14:10:56
StevenKin: 感觉spring的原理也是这样 👍🏽0 💭N/A IP 🕐2021-10-19 10:41:24
释迦摩尼: Object.getClass()结果是什么 👍🏽0 💭N/A IP 🕐2018-01-12 23:03:11
│ └── RednaxelaFX: 这是一个实例方法,返回的是一个java.lang.Class的实例,用于表示对象的实际类型对应的类反射信息。java.lang.Class的基类是什么? 👍🏽0 💭N/A IP 🕐2018-01-12 23:47:28
│ └── 释迦摩尼: 大神回复,有点激动,谢谢您。没有认真看完,就问问题了。不好意思。整篇看下来,豁然开朗。不说了,拜读大神的文章去了。 👍🏽0 💭N/A IP 🕐2018-01-13 10:36:38
wind2412: 莫非这也是 java.lang.Class 只有一个空构造函数的原因…? 因为这样创建 mirror 的时候就不用往 oopDesc 里边注入数据了…? 因为我观察 hotspot 在产生 “暂时不可用的 String” 的时候,仅仅是分配内存并向内注入了一个 TypeArrayKlass 的 oop 引用作为伪的构造函数… 👍🏽0 💭N/A IP 🕐2017-11-15 17:20:56
│ └── RednaxelaFX: java.lang.Class实例是完全被HotSpot控制的,只能直接由VM创建。这是构造器留空的主要原因—Java代码不应该能够创建出实例来所以也没必要有有内容的构造器 👍🏽0 💭N/A IP 🕐2017-11-15 17:34:09