对象是如何被创建的?

(本文章为本人自媒体账号“小刘不是程序员”发布视频的文字版,方便大家阅读)

这篇文章以Java为例,浅显地解释一下JVM当中,Java对象是如何被创建的。

我们以这段代码为例子:

class Person { String name; int age; Person(String name, int age) { this.name = name; this.age = age; } }

一个再普通不过的Person类,包含name和age两个字段。在main中我们创建一个Person类的实例,然后把这个实例的name打印出来:

Person a = new Person("小刘", 18); System.out.println(a.name);

编译这段代码会生成一个class文件,我们用 javap 工具可以把class文件当中的字节码打印出来:

143: new #55 // class com/skyline/Person 146: dup 147: ldc #57 // String 小刘 149: bipush 18 151: invokespecial #59 // Method com/skyline/Person."<init>":(Ljava/lang/String;I)V

可以看到在字节码中,关于对象创建第一条也是最重要的一条指令,就是 new。后面的 #55 是对类型的引用。输出也展示了,指向的就是代码里写的Person类型。字节码的任务到这儿就算完成了,具体怎么 new 这个对象呢?这就是 JVM 需要解决的问题了。

Hotspot 虚拟机的代码量巨大。为了让大家更好的理解创建对象的过程,我们先不看具体的代码,而是了解一下从高层角度看,Hotspot 中 Java 对象是如何存在的。

我们知道在 Java 中,除了基础类型之外的对象都继承自

java.lang.Object
,但这仅仅是停留在 Java 层面。Hotspot 是用 C++ 实现的,它需要用某种方式来实现 Java 对象的各种功能。Hotspot 使用的方式被称为 oop/klass 模型。Klass 和 oop 是两个 C++ 类,一个 Klass 对应一个 Java 类,而一个 oop 对应一个 Java 对象。回到最开始的代码例子,在Java层面有一个 Person 类,意味着C++层面就有一个 Klass 实例对应Person类。而我们创建的a这个对象,在C++层面就是一个 oop 实例。

为什么要拆成两个东西呢?在 Hotspot 源码里有解释。klass 头文件里的注释是这样说的:

// One reason for the oop/klass dichotomy in the implementation is // that we don't want a C++ vtbl pointer in every object. Thus, // normal oops don't have any virtual functions. Instead, they // forward all "virtual" functions to their klass, which does have // a vtbl and does the C++ dispatch depending on the object's // actual type.

分开 oop/klass 的一个原因是,我们不想让每个对象里都有 C++ vtbl 指针。正常的oop对象里没有任何虚方法,它们将所有的”虚“方法转发给对应的 klass。而 klass 中包含 vtbl,并且可以根据实际类型进行 C++ 的动态调用。

由于拆分了权责,oop的实现相当简单。这就是 oop 的实现类,名字叫做 oopDesc:

class oopDesc { //... private: volatile markWord _mark; union _metadata { Klass* _klass; narrowKlass _compressed_klass; } _metadata; //... }

它里面就俩字段,一个是 MarkWord,另一个是 metadata。这俩东西就是很多文章中都会提到的 Java 对象头。其中 metadata 里有一个指向 Klass 的指针,对象跟类就这么被联系起来了。

聪明的同学可能已经想到一个问题了,对象也不能只有头啊。比方我们的 Person 里面有 name 有 age,这些东西存哪儿呢?答案是直接往 oopDesc 的后面存,这就是 Hotspot 里对 C++ 的利用和扩展了。oopDesc 里面的对象头,紧接着后面就是一个 int 也就是 age,然后就是一个 String 引用也就是 name。顺序的话可能跟代码定义的不完全相同,大家不用在意。再往后,可能还会有 padding 填充。

为了方便获取后面的各种字段,oopDesc 提供了 field_addr,obj_field_addr 等方法。它们的实现就是拿当前对象的指针,然后往后面加 offset 算:

void* oopDesc::field_addr(int offset) const { return reinterpret_cast<void*>(cast_from_oop<intptr_t>(as_oop()) + offset); }

有了这些基础知识之后,再去理解 JVM 源代码就会简单很多。下面我们看一下 Hotspot 中bytecodeInterpreter里 new 这个指令的实现。bytecodeInterpreter是一个解释型的实现,并不是实际上跑的实现。不过用来理解VM工作过程是没问题的,《深入理解Java虚拟机》中也是用它做的例子。这就是new的实现:

ConstantPool* constants = istate->method()->constants(); if (UseTLAB && !constants->tag_at(index).is_unresolved_klass()) { Klass* entry = constants->resolved_klass_at(index); InstanceKlass* ik = InstanceKlass::cast(entry); if (ik->is_initialized() && ik->can_be_fastpath_allocated()) { size_t obj_size = ik->size_helper(); HeapWord* result = THREAD->tlab().allocate(obj_size); if (result != NULL) { // Initialize object field block: // - if TLAB is pre-zeroed, we can skip this path // - in debug mode, ThreadLocalAllocBuffer::allocate mangles // this area, and we still need to initialize it if (DEBUG_ONLY(true ||) !ZeroTLAB) { size_t hdr_size = oopDesc::header_size(); Copy::fill_to_words(result + hdr_size, obj_size - hdr_size, 0); } oop obj = cast_to_oop(result); // Initialize header assert(!UseBiasedLocking, "Not implemented"); obj->set_mark(markWord::prototype()); obj->set_klass_gap(0); obj->set_klass(ik); // Must prevent reordering of stores for object initialization // with stores that publish the new object. OrderAccess::storestore(); SET_STACK_OBJECT(obj, 0); UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1); } } }

首先可以看到它的主逻辑是,先判断用不用 TLAB 也就是线程本地存储。如果使用线程本地存储的话,就不用去全局堆上建对象了,也就不需要线程间的同步了。首先它拿到了一个 Klass 的实现,也就是要先拿到类的有关信息。然后cast成了,InstanceKlass 这个 Klass 的子类。再往后它算了下整个对象的大小,然后去 TLAB 里分配了对应的大小,得到了 result 这个内存指针。然后把 result 这个指针 cast 成 oopDesc * 类型设置上默认的 markword 以及 metadata 信息,这个对象在 Hotspot 层面就已经创建完成了。

回到最开始的反编译结果,我们会看到后面其实还有一个重要指令就是 invokespecial:

151: invokespecial #59 // Method com/skyline/Person."<init>":(Ljava/lang/String;I)V

它用来调用对象的init,也就是Java里的constructor。constructor里面就是Java程序员可以控制的了。完成这一步之后,整个Java对象才算是在Java层面可用了。

#后端#
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
小刘
作者分享
对象是如何被创建的?
23
入职不是上岸,学习没有终点 看到最近阿里又在裁员的消息,让我也想起了自己当年被阿里“拥抱变化”的故事。我人生中第一份实习来自阿里,踩的第一个“坑”就来自这份实习。 15年暑假我通过面试拿到了杭州天猫的iOS实习岗位,从哈尔滨飞到杭州,开启了人生中第一段实习。当时人也比较年轻,又进入了所谓的大厂,说实话是有一点飘的,当时已经决定以后留在阿里,留在杭州发展。 顺利入职之后,我开始了正常的实习工作,在天猫也确实学到了很多。美好的未来几乎快要在眼前慢慢展开了。结果实习三个月后,一封发给所有实习生的邮件,给了我当头一棒。马总这封题为“拥抱变化”的邮件,通知大家当年的实习生裁撤比例会超过往年,大批的实习生将不会留下。我成为了没有被留下的大多数人。 现在想想,实习留不下其实是一个非常正常的事。但对于当时涉世不深的我来说,这事儿仿佛是一个天大的打击。我非常慌张地在杭州又待了一个月,期间找到了网易在内的一些公司,参加了它们的面试看看有没有机会。不过由于一直在实习工作,事发突然,也没有什么准备时间,面试状态不好,各种面试都没有通过。眼看在杭州没有什么机会了,我只能灰溜溜地回到了河北老家。 当时在家里,整个人其实都是颓废的。说白了之前的路太顺了,没受过这种挫折。幸亏有爸妈的鼓励,让我重新找到了动力。经过一些面试,我找到了北京一家中小公司的iOS岗位,然后起身去北京实习。 小公司虽然比不上阿里,但是也能学到不少东西,在这家小公司里,我认识了后来帮了我很多的前辈,也学到了不少有用的技术知识。虽然小公司给的工资不如阿里,也没有所谓大厂“光环”,但是我心想着,起码是个工作,留在北京也不错。结果两个月之后,现实又给了我当头第二棒。这家小公司也没有让我留下,给出的理由到现在我都印象深刻:“我没有足够的正能量”。 怎么回事呢?后来我反思了一下自己,在技术层面大家对我都是认可的,但是在职场层面,当时的我年少轻狂,经常直接指出别人的代码和设计上的问题,语气上非常不留情面,有时候还会带着情绪。这样的表现,在小团队里或许确实是难以接受的。大家也可以自查一下,有没有类似的问题,如果有的话还是要尽快调整自己。 又没有留下,当时我已经开始怀疑自己了,为什么自己这么差呢?怎么办呢?没有办法,只能重新准备面试,后来经过几番辗转,最后我通过了猿题库(猿辅导)的面试。当时HR还是想让我实习一段,我以时间不充足为例拒绝了。而实际上,我是因为对实习这事有一些阴影了。毕业之后我入职了猿辅导,工作能力也没有让领导失望,承接了若干重要工作,我的职场之路终于算是顺利了一些。 说了这么多我的故事,我想有这么几点是可以对大家有用的: 1.挫折教育是我们的教育体系内缺失的一环。对没有背景的普通人来说,人生中十有八九是不如意的,越早认清这点,越早能避免挫折带来的负面影响,轻装上路。 2.否认自己不如提升自己。陷入到否认自己的情绪中,只会让自己更加焦虑。行动起来,才是缓解焦虑的最佳方法。 3.入职不是上岸。不管是实习还是正式,都有可能面临裁撤的风险,提升自己的能力,才能让自己有更宽广的选择空间。 4.学习没有终点。星球内很多同学都会提到在学校感觉很卷,而职场的卷想必大家都感受到了,强如阿里也会有被裁的风险。这种卷带来的影响就是,不学习就会落后。因此对未来保持适当的警惕感,能更加激励自己学习。 5.社会比职场还卷。像我们的鱼总已经不满足于在程序员这个群体卷了,先是进入自媒体领域和一帮博主卷,现在又开始创业,和更多对手展开商业上的竞争。鱼总能做到这点,背后付出的是很多人想象不到的努力。从这个角度看,鱼总确实是我们每个人的学习榜样。 最后送给大家一句忘了在哪儿看到的话: 当你对未来完全没有焦虑的时候,你应该有所担心。当你对未来过于焦虑的时候,你应该放宽心态。听起来很矛盾,但是很多事情就是要在黑和白中,找到那个灰。在焦虑和躺平中,找到每个人自己觉得最舒服的平衡点。 共勉。 #求职# #职场# #经验#
220
入职不是上岸,学习没有终点 看到最近阿里又在裁员的消息,让我也想起了自己当年被阿里“拥抱变化”的故事。我人生中第一份实习来自阿里,踩的第一个“坑”就来自这份实习。 15年暑假我通过面试拿到了杭州天猫的iOS实习岗位,从哈尔滨飞到杭州,开启了人生中第一段实习。当时人也比较年轻,又进入了所谓的大厂,说实话是有一点飘的,当时已经决定以后留在阿里,留在杭州发展。 顺利入职之后,我开始了正常的实习工作,在天猫也确实学到了很多。美好的未来几乎快要在眼前慢慢展开了。结果实习三个月后,一封发给所有实习生的邮件,给了我当头一棒。马总这封题为“拥抱变化”的邮件,通知大家当年的实习生裁撤比例会超过往年,大批的实习生将不会留下。我成为了没有被留下的大多数人。 现在想想,实习留不下其实是一个非常正常的事。但对于当时涉世不深的我来说,这事儿仿佛是一个天大的打击。我非常慌张地在杭州又待了一个月,期间找到了网易在内的一些公司,参加了它们的面试看看有没有机会。不过由于一直在实习工作,事发突然,也没有什么准备时间,面试状态不好,各种面试都没有通过。眼看在杭州没有什么机会了,我只能灰溜溜地回到了河北老家。 当时在家里,整个人其实都是颓废的。说白了之前的路太顺了,没受过这种挫折。幸亏有爸妈的鼓励,让我重新找到了动力。经过一些面试,我找到了北京一家中小公司的iOS岗位,然后起身去北京实习。 小公司虽然比不上阿里,但是也能学到不少东西,在这家小公司里,我认识了后来帮了我很多的前辈,也学到了不少有用的技术知识。虽然小公司给的工资不如阿里,也没有所谓大厂“光环”,但是我心想着,起码是个工作,留在北京也不错。结果两个月之后,现实又给了我当头第二棒。这家小公司也没有让我留下,给出的理由到现在我都印象深刻:“我没有足够的正能量”。 怎么回事呢?后来我反思了一下自己,在技术层面大家对我都是认可的,但是在职场层面,当时的我年少轻狂,经常直接指出别人的代码和设计上的问题,语气上非常不留情面,有时候还会带着情绪。这样的表现,在小团队里或许确实是难以接受的。大家也可以自查一下,有没有类似的问题,如果有的话还是要尽快调整自己。 又没有留下,当时我已经开始怀疑自己了,为什么自己这么差呢?怎么办呢?没有办法,只能重新准备面试,后来经过几番辗转,最后我通过了猿题库(猿辅导)的面试。当时HR还是想让我实习一段,我以时间不充足为例拒绝了。而实际上,我是因为对实习这事有一些阴影了。毕业之后我入职了猿辅导,工作能力也没有让领导失望,承接了若干重要工作,我的职场之路终于算是顺利了一些。 说了这么多我的故事,我想有这么几点是可以对大家有用的: 1.挫折教育是我们的教育体系内缺失的一环。对没有背景的普通人来说,人生中十有八九是不如意的,越早认清这点,越早能避免挫折带来的负面影响,轻装上路。 2.否认自己不如提升自己。陷入到否认自己的情绪中,只会让自己更加焦虑。行动起来,才是缓解焦虑的最佳方法。 3.入职不是上岸。不管是实习还是正式,都有可能面临裁撤的风险,提升自己的能力,才能让自己有更宽广的选择空间。 4.学习没有终点。星球内很多同学都会提到在学校感觉很卷,而职场的卷想必大家都感受到了,强如阿里也会有被裁的风险。这种卷带来的影响就是,不学习就会落后。因此对未来保持适当的警惕感,能更加激励自己学习。 5.社会比职场还卷。像我们的鱼总已经不满足于在程序员这个群体卷了,先是进入自媒体领域和一帮博主卷,现在又开始创业,和更多对手展开商业上的竞争。鱼总能做到这点,背后付出的是很多人想象不到的努力。从这个角度看,鱼总确实是我们每个人的学习榜样。 最后送给大家一句忘了在哪儿看到的话: 当你对未来完全没有焦虑的时候,你应该有所担心。当你对未来过于焦虑的时候,你应该放宽心态。听起来很矛盾,但是很多事情就是要在黑和白中,找到那个灰。在焦虑和躺平中,找到每个人自己觉得最舒服的平衡点。 共勉。 #求职# #职场# #经验#
215
Hello World是如何被打印的?
35
如何处理和领导的关系? 看到星球里面有人发了刚入职不久被领导骂的帖子,也看到了小林兄的文章,有感而发,趁这个机会也想说两句。 首先一个很残酷的事实是,和进入社会之后的大部分选择一样,能不能碰到一个好领导这事儿,具有相当大的随机性。你可能在校招的时候选择了一家大厂,但是却分到了一个喜欢PUA的领导。或者反过来进入了一个中小厂,但是领导本身人还不错,愿意帮助你成长。 对于刚毕业的同学,这种随机性带来的影响是比较大的,因为刚进入一家公司,不管是技术经验还是话语权上,都处于弱势。再加上现在就业环境确实不好,也很难有人因为被领导骂两句就拍拍屁股走人,放弃来之不易的岗位。顺便提一句,和人生中可能面临的各种曲折来说,被骂两句真的不是什么特别重大的事儿。 我能给大家的建议有两个,第一是抽离自己的主观感受和情绪,从客观的角度思考一下,为什么会被骂?是自己能力确实没有不够,还是领导本身的要求就是过分的?或者两者都有?思考清楚这一点,才能矫正自己的心态,找到以后努力的方向。 如果是自己能力确实没有达到领导的预期,那就要快速地做出反应,提升自己的技术积累。我记得我刚入职的时候,第一个做的任务完成的也并不出色,来回来去改了很多遍,最后还是没有达到领导的要求。所幸领导很有耐心,最后他自己上手写完了那个模块,那件事之后我深刻意识到了自己的水平欠缺,学习的时候紧迫感更强了,也更有动力了,提升的速度也相当快。抗压能力,以及压力之下的学习能力,本身也是一种非常重要的软技能。大家在工作几年之后,对这点理解会更加深刻。 如果领导本身的要求就是过分的,我给出的第二个建议就是,勇敢地去和领导沟通。大家都是打工人,职级上有高低,人格上并无贵贱。领导可能会对某个人有不满情绪,但是本质上他的终极目标也只是完成工作任务。只要你的沟通内容是合理的,正向的,有助于公司发展的,就可以大胆地表达自己的意见,坦诚地提出自己的需求。如果领导愿意采纳,当然是最好的,没有采纳也没关系,就当是锻炼自己的沟通能力了。沟通和表达能力,也是职场中非常重要的软技能,表达能力强了之后,你甚至可以像我们这些嘉宾一样,搞个副业做自媒体[机智] 这篇文章结尾,为了呼应一下开头,我想再聊聊职场的残酷。做自媒体之后,有很多大学生朋友咨询我,我都会劝他们,好好珍惜大学时光,因为那可能是一生中时间最自由,精力最充沛,学东西最快的阶段了。工作之后不管是生计带来的压力,选择带来的焦虑,以及生活的各种琐事,都会让我们更难找到时间提升自己。没有办法,人总是要长大的,希望看到这篇文章的朋友,都能着眼未来,珍惜现在。 共勉。 #职场# #求职# #经验#
50
下载 APP