深入理解JVM系列(五)阿里面试题-关于对象Object o = new Object()灵魂6问

如果觉得写的还可以请关注微信公众号:程序猿的日常分享,定期更新分享。

请解释一下对象的创建过程?

1、加载
2、链接(验证、准备、解析)
3、初始化
4、申请对象内存
5、成员变量赋默认值
6、调用构造方法<init>:1)成员变量顺序赋初始值 2)执行构造方法语句

对象在内存中的存储布局?

jvm中的对象分为两种,一种是普通对象,一种是数组对象。这两种对象在内存中的布局是不一样的。如下图所示:


image.png

普通对象new Object()有4部分组成,分别是对象头、类型指针、实例数据、填充。
数组对象int i = new int[4]有5部分组成,分别是对象头、类型指针、数组长度、实例数据、填充。

  • 对象头(Mark Word)
    用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机中分别为4个字节和8个字节,官方称它为 Mark Word。

  • 类型指针(Class Pointer)
    存储对象所属类的地址,就是为了标记到底是什么类的实例。jvm默认开启了指针压缩,所以占用4个字节,如果关闭指针压缩,就占用8个字节。此外,指针压缩还会影响instance data的实例对象的指针空间占用大小。如果开启了指针压缩,Long型的成员变量和long型的成员变量占用空间大小是有区别的:Long占用4个字节;long是基础类型占用8个字节。如果关闭了指针压缩:Long占用8个字节;long是基础类型占用8个字节。
    Hotspot开启内存压缩的规则(64位机):
    1、4G以下,直接砍掉高32位
    2、4G - 32G,默认开启内存压缩 ClassPointers Oops
    3、32G,压缩无效,使用64位,所以内存并不是越大越好

  • 实例数据(Instance Data)
    实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录下来。父类定义的变量会出现在子类定义的变量的前面。各字段的分配策略为longs/doubles、ints、shorts/chars、bytes/boolean、oops(ordinary object pointers),相同宽度的字段总是被分配到一起,便于之后取数据。

  • 填充(Padding)
    填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。为什么需要有对齐填充呢?由于JVM读数据时是按照一块一块的读取的,这样读取效率更高,64位虚拟机的话对象的大小必须是8字节的整数倍。因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

  • 数组长度(Length)
    存储了数组对象的长度,占用4个字节

对象头具体包括什么?

Mark Word的结构,定义在markOop.hpp文件中,其中定义了32位是怎么实现的,64位是怎么实现的。源码如下:

image.png

以64位虚拟机来看翻译过来以后如下:
image.png

1、当我们创建一个无锁态对象的时候:25位没有用;31位装的identity Hashcode,但是只有在被调用的时候,才填充,没有调用的时候是空的;1位没有使用的;4位分代年龄(解释在下面);1位偏向锁位;2位锁标志位。
2、偏向锁的时候:54位存下当前线程的ID;2位存批量撤销Epoch;1位没有使用;4位分代年龄;1位偏向锁位;2位锁标志位。
3、 自旋锁:62位指向线程中的Lock Record的指针。Lock Record与锁重入有关,synchronize默认是可重入的。自旋锁在竞争锁的时候,会在自己的内存的线程栈中创建一个Lock Record对象,抢到锁对象的资源时,锁对象头存的就是这个线程的Lock Record对象的指针,所以在重入的时候,会再创建一个Lock Record对象,利用Lock Record来记录到底琐了多少次。解锁的时候,就将一个Lock Record移除,移除的方式是FILO,也就是先进后出的原则。
4、 重量级琐:重量级琐是在C++代码层面进行的,会生成一个ObjectMonitor对象,这个对象中记录了一系列的队列。
5、分代年龄:JVM有10种垃圾回收器,前面7种都涉及分代年龄,采用分代算法。当我们创建一个对象的时候,把它放在年轻代中,每经过一次垃圾回收后年龄就+1,也就是垃圾回收无法回收掉这个对象,它的年龄就会不断的增长,到达15,因为4个字节最大为15,就转到老龄代,年轻代的回收就不再对它进行回收。
6、hashCode部分:对象头上的hashCode并不是我们调用重写的hashCode()方法生成的,而是为重写的hashCode()方法或者调用System.identityHashcode()方法才能获取并且存入对象头中。通俗来讲,这里的hashCode是按照原始内容计算的,重写过的hashCode()方法计算的结果并不会存在此处。如果对象没有重写hashCode()方法,那么默认调用的os::random产生hashCode,也可以通过System.identityHashcode()获取。os::random产生hashCode的规则是:next_rand = (16807seed)mod(2*31-1),因此可以使用31位存储空间进行存储,并且一旦产生这个hashCode,JVM就会记录在mark word中。

关于锁有几个需要注意的地方:

1、当一个对象已经计算过identity hash code,则它就无法进入偏向锁状态。因为如果已经计算过identity hash code的值以后,在上图中偏向锁记录线程ID的内存已经被占用了。
2、当一个对象正处于偏向锁状态,并且需要计算identity hash code的话,则它的偏向锁会被撤销、膨胀为重量级锁
3、重量级锁的实现中,ObjectMonitor类里有字段可以记录非加锁状态下的mark word,其中可以存储identity hash code的值。

对象怎么定位

JVM中对象访问定位两种方式:
1、直接指针访问:Java栈直接与对象进行访问,在Java堆中对象帆布中必须考虑存储访问类型的数据的相关信息 ,直接指针访问的优点比较明显,就是访问速度快,不需要和句柄一样指针定位的开销 。缺点也比较明显,就是对象在GC过程中,在新生代区域复制移动时,会比较麻烦。如下图:


image.png

2、通过句柄池方式访问:在Java堆中分出一块内存进行存储句柄池,在栈中存储的是句柄的地址,通过句柄池访问有独特的优点,就是当对象移动的时候(垃圾回收的时候移动很普遍),这样值需要改变句柄中的指针,但是栈中的指针不需要变化,因为栈中存储的是句柄的地址。那么对应的缺点就是需要两次指针转换进行访问,访问速度比直接指针访问稍慢一些。如下图:


image.png

对象怎么分配

对象分配流程如下图:


image.png

1、当我们new出一个对象,JVM会首先尝试往栈上分配,如果能够分配得下,就分配到栈上分配到栈上的对象有好处就是不需要GC进行管理,什么时候不需要用到此对象了,将对象出栈就可以了。但是分配到栈上的对象是有要求的:第一,对象比较小,因为栈空间本来就不够大;第二,对象比较简单。
2、如果栈上分配不下,我们就判断这个对象是不是够大,如果足够大就直接放在老年代区,在老年代区的对象经过一次全量垃圾回收FGC后,才有可能被回收掉。
3、如果如果栈上分配不下并且对象不大,就会判断对象能否被存在线程本地分配缓冲区-TLAB(Thread Local Allocation Buffer)。但是不管放不放得下,都是放在新生代区的伊甸区eden。 但是因为堆是共享的,多个线程可以同时创建对象就可能会争夺同一块内存区域,所以为了保证线程安全,Eden区又被分配成一个个线程本地分配缓冲区,这个TLAB是线程私有的,每个线程都有自己的TLAB,避免了多线程环境下使用同步技术带来的性能损耗。
4、伊甸区eden的对象在经过一次GC后,如果被回收掉了,那就结束了生命周期。
5、伊甸区eden的对象在经过一次GC后,如果没有被回收掉,会被拷贝到幸存者区survivor1,对比上面的堆内存逻辑分区图。幸存者区survivor1中的对象再经过一次GC后如果对象还存活,那么就拷贝到幸存者区survivor2并且清理掉幸存者区survivor1中的所有对象,再有GC就反复这个操作,直到对象的分代年龄达到了移到老年代的界限(一般默认是15),就会被移到老年代中。

Object o = new Object()在内存中占用多少字节?

这里考察的知识点是对象在内存中的存储布局结构和类指针以及普通对象指针的概念。存储布局不再多说,类指针就是存储布局中的class pointer,普通对象指针就是存储布局中的instance data中,成员变量如果不是基础类型而是引用类型,那么也会有普通对象指针指向所属类。默认情况下JVM是开启了类指针和普通对象指针的指针压缩,将8个字节压缩成了4个字节。我们用代码输出来观察对象的大小,实验代码如下:


image.png

(1) 默认开启所有指针压缩的情况下输出如下:


image.png

(2) 关闭类指针压缩后,如下:


image.png

如果觉得写的还可以请关注微信公众号:程序猿的日常分享,定期更新分享。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342