STOP,废话结束
创新互联公司是专业的新津县网站建设公司,新津县接单;提供成都网站建设、网站建设,网页设计,网站设计,建网站,PHP网站建设等专业做网站服务;采用PHP框架,可快速的进行新津县网站开发网页制作和功能扩展;专业做搜索引擎喜爱的网站,专业的做网站团队,希望更多企业前来合作!
今天介绍两个 JVM 中的高频基础题:
对象的创建过程分五步走,如下图:
我感觉 JVM 如果不看 GC 收集器那块(滑稽),似乎东西还不多
老规矩,背诵版在文末。点击阅读原文可以直达我收录整理的各大厂面试真题
对象创建过程的第一步,所谓类加载检查,就是检测我们接下来要 new 出来的这个对象所属的类是否已经被 JVM 成功加载、解析和初始化过了(具体的类加载过程会在后续文章详细解释~)
具体来说,当 Java 虚拟机遇到一条字节码 new 指令时:
1)首先检查根据 class 文件中的常量池表(Constant Pool Table)能否找到这个类对应的符号引用
此处可以回顾一波常量池表 (Constant Pool Table) 的概念:
用于存放编译期生成的各种字面量(字面量相当于 Java 语言层面常量的概念,如文本字符串,声明为 final 的常量值等)与符号引用。有一些文章会把 class 常量池表称为静态常量池。
都是常量池,常量池表和方法区中的运行时常量池有啥关系吗?运行时常量池是干嘛的呢?
运行时常量池可以在运行期间将 class 常量池表中的符号引用解析为直接引用。简单来说,class 常量池表就相当于一堆索引,运行时常量池根据这些索引来查找对应方法或字段所属的类型信息和名称及描述符信息
2)然后去方法区中的运行时常量池中查找该符号引用所指向的类是否已被 JVM 加载、解析和初始化过
如果没有,那就先执行相应的类加载过程
如果有,那么进入下一步,为新生对象分配内存
类加载检查通过后,这个对象待会儿要是被创建出来得有地方放他对吧?
所以接下来 JVM 会为新生对象分配内存空间。
至于 JVM 怎么知道这个空间得分配多大呢?事实上,对象所需内存的大小在类加载完成后就已经可以完全确定了。在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:对象头、实例数据和对齐填充。
1)Hotspot 虚拟机的对象头包括两部分信息:
2)实例数据部分存储的是这个对象真正的有效信息,即我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来。
3)对齐填充部分不是必须的,也没有什么特别的含义,仅仅起占位作用。因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
为对象分配内存空间的任务通俗来说把一块确定大小的内存块从 Java 堆中划分出来给这个对象用。
根据堆中的内存是否规整,有两种划分方式,或者说对象在堆上的分配有两种方式:
1)假设 Java 堆中内存是绝对规整的,所有被使用过的内存都被放在一边,空闲的内存被放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把这个指针 向 空闲空间方向 挪动一段与对象大小相等的距离,这种分配方式称为 指针碰撞(Bump The Pointer)
2)如果 Java 堆中的内存并不是规整的,已被使用的内存和空闲的内存相互交错在一起,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的连续空间划分给这个对象,并更新列表上的记录,这种分配方式称为 空闲列表(Free List)。
选择哪种分配方式由 Java 堆是否规整决定,那又有同学会问了,堆是否规整又由谁来决定呢?
Java 堆是否规整由所采用的垃圾收集器是否带有空间压缩整理(Compact)的能力决定的(或者说由垃圾收集器采用的垃圾收集算法来决定的,具体垃圾收集算法见后续文章):
因此,当使用 Serial、ParNew 等带压缩整理过程的收集器时,系统采用的分配算法是指针碰撞,既简单又高效
而当使用 CMS 这种基于清除(Sweep)算法的收集器时,理论上就只能采用较为复杂的空闲列表来分配内存
另外,在为对象创建内存的时候,还需要考虑一个问题:并发安全问题。
对象创建在虚拟机中是非常频繁的行为,以上面介绍的指针碰撞法为例,即使只修改一个指针所指向的位置,在并发情况下也并不是线程安全的,可能出现某个线程正在给对象 A 分配内存,指针还没来得及修改,另一个线程创建了对象 B 又同时使用了原来的指针来分配内存的情况。
解决这个问题有两种可选方案:
内存分配完成之后,JVM 会将分配到的内存空间(当然不包括对象头啦)都初始化为零值,比如 boolean 字段都初始化为 false 啊,int 字段都初始化为 0 啊之类的
这步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,使程序能访问到这些字段的数据类型所对应的零值。
如果使用了 TLAB 的话,初始化零值这项工作可以提前至 TLAB 分配时就顺便进行了
上面我们说过,对象在内存中的布局可以分为 3 块区域:对象头(Object Header)、实例数据和对齐填充
对齐填充并不是什么有意义的数据,实例数据我们在上一步操作中进行了初始化零值,那么对于剩下的对象头中的信息来说,自然不必多说,也是要进行一些赋值操作的:例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
上面四个步骤都走完之后,从 JVM 的视角来看,其实一个新的对象已经成功诞生了。
但是从我们程序员的视角来看,这个对象确实是创建出来了,但是还没按照我们定义的构造函数来进行赋值呢,所有的字段都还是默认的零值啊。
构造函数即 Class 文件中的 () 方法,一般来说,new 指令之后会接着执行 ()方法,按照构造函数的意图对这个对象进行初始化,这样一个真正可用的对象才算完全地被构造出来了,皆大欢喜。
最后放上这道题的背诵版:
???? 面试官:讲一下对象的创建过程
小牛肉:new 一个对象在堆中的过程主要分为五个步骤:
1)类加载检查:具体来说,当 Java 虚拟机遇到一条字节码 new 指令时,它会首先检查根据 class 文件中的常量池表(Constant Pool Table)能否找到这个类对应的符号引用,然后去方法区中的运行时常量池中查找该符号引用所指向的类是否已被 JVM 加载、解析和初始化过
2)分配内存:就是在堆中给划分一块内存空间分配给这个新生对象用。具体的分配方式根据堆内存是否规整有两种方式:
3)初始化零值:对象在内存中的布局可以分为 3 块区域:对象头、实例数据和对齐填充,对齐填充仅仅起占位作用,没啥特殊意义,初始化零值这个操作就是初始化实例数据这个部分,比如 boolean 字段初始化为 false 之类的
4)设置对象头:这个步骤就是设置对象头中的一些信息
5)执行 init 方法:最后就是执行构造函数,构造函数即 Class 文件中的 ()方法,一般来说,new 指令之后会接着执行() 方法,按照构造函数的意图对这个对象进行初始化,这样一个真正可用的对象才算完全地被构造出来了
网站题目:New 一个对象在堆中的历程
文章起源:http://www.csdahua.cn/qtweb/news21/110121.html
网站建设、网络推广公司-快上网,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 快上网