这么把一个map对象序列化_Java new 一个对象居然要这么多流程~

程序员们经常会调侃说,现在找对象太难了,看我们代码里找对象多容易,想要的时候就new 一个。

玩笑归玩笑,实际对代码来说,对象不是你想 newnew就能 new 的。像真实社会里会见见家长,了解家庭等等,在实际的代码运行中,一个也不少。一个对象 new 的过程,也是要经过层层「 考核」的。

今天一起看下, 代码里 new 一个对象,都有哪些过程。

在 Java 开发中,你能想到的对象创建过程,最直接体现的是代码里

new MyObject()

然而这只是「冰山一角」。

在虚拟机执行中,要在堆里给对象空间吧。比如最容易想到的,你需要的对象有点大,在new的时候,内存不够了。

又或者当前创建对象的 Class 继承或者组合了其它的类或接口,这些在类加载过程中没有找到,和现实中的家长不同意也差不多。

还有多线程环境下,对象可能过早的「逸出」,就像家长让对象去相亲一样,让其他线程可以修改对象的值,让你意想不到。

...

你以为只是轻松的 new 了一下, 但JVM 还真干了不少事儿吧~ JVM 默默承担了这一切,化难度、复杂于无形。

比如为了让你更快的有对象, JVM 会看情况能不能「快速分配」,符合条件甚至还有专有内存空间,保证不受其他线程的影响等等, 如果还是不行只能慢速了。

一个对象的创建基本流程如下:

0. 获得对象在常量池中的索引以及对应的常量池项

  1. 验证类是否已被解析
  2. 获取他对应的 instanceKlass,保证已经完成初始化
  3. 如果满足条件,进入快速分配流程
  4. 不满足或快速分配失败,进入慢速分配,再进行类的初始化,实例分配

总结起来似乎也只有两步:

  1. 给对象分配 空间
  2. 设置对象的引用

但是分配就有不少的细节。为了能进一步提速分配,并不是所有的对象都在 Eden区来分内存的。

5cf5176f129f4493ccd272694b978506.png

你想啊,整个堆都是线程共享的,如果大家都在这里分内存,就免不了加锁的操作。为此, JVM 又提供了一个加速选项。在 Eden 里给线程分配自己的私有分配区域,也就是传说中的「TLAB(Thread Local Allocation Buffers) 」。这样每个线程分配的时候,因为是线程私有,就不再需要加锁,可以快速搞定。分配失败就会再尝试加锁在 Eden 中进行空间分配。

d8770ebdda16534bd969f11a0043b168.png

有没有感觉 TLAB的分配和动物占领领地类似,把某块区域拉上便便或者撒上尿,以此提醒其他的动物,这个地方被占领了。 :-)

生活中的例子,有点类似计次的VIP通道。比如你在某个理发馆充值办了VIP,够10次剪发。那这10次之内去的时候,都不需要和其他人一起排队等,直接分配固定的师傅。那如果还有一次的时候,你是去烫头,原有的VIP不够用了,就需要在「烫头」的资源里去排队。

在「虚拟机设计与实现」一书中,对于线程局部分配是这样描述的:

跳增指针分配只可用于空闲空间属于单个线程的情况。如果有多个线程,那分配应用是线程安全的。为每个对象分配使用原子指令代价过去昂贵。一个常见的解决方案是只对块分配使用原子操作。每个线程从全局空间空间中用原子指令抓取一个空闲块,然后在块中用跳增指针进行对象分配,不用原子操作,这个块是线程局部用于分配的。

TLAB 该设置多大合适呢?

毕竟分配这个块也还是需要原子操作的。如果太小,就需要频繁的去请求块的分配,从而让线程局部块的优势打了折扣。

但块也不能太大,否则如果应用程序中有很多线程,有些线程可能并不活跃分配对象,就会浪费其中只有少数几个对象的块空间。

TLAB的大小 在 HotSpot 中默认是自动调整的,根据线程数以及每次创建对象的大小等一系列来自动评估。

当然,你也应该想到,如果你的对象空间占用大怎么办?

所以,在进行TLAB分配的时候会判断现有的余量是否够分配对象。如果够还好,直接分配,如果不够的话,看余下有多少,如果余下的大于允许浪费的量,则会直接在Eden上分配,不走TLAB,否则的话,再申请一块TLAB,继续分配。

对象大小怎么计算呢? 之前有一篇文章,怎样计算一个 Java 对象大小?这儿有几种方法~

这种TLAB的分配形式,也被称为「跳增指针分配」,也就是说分配一个对象的时候,只需要在空闲空间中移动指针,跳增对象大小。

如果TLAB分配失败就只能在 Eden中不停的重试去分配空间。

分配空间之后, 虚拟机会根据配置,确定是否要对实例数据进行填0操作。这样后面 Java 代码里可以不初始赋值就能直接使用了。

再进行对象头的初始化操作 更新栈顶对象引用

慢速分配 则需要在分配实例前,对类进行解析,确保类和依赖的类已经都进行了解析和初始化。

首先需要对类检查,确保不是抽象类和接口;

然后确保类已经初始化,计算对象大小,在堆上创建实例

最后设置线程栈中的对象引用

怎么样? 代码里找个对象也不是那么容易吧 :-)

这其中有一些底层 JVM 的分配细节并没有描述。你有哪些了解的内容呢,欢迎留言一起交流。

参考:

『HotSpot 实战』

『虚拟机设计与实现』

https://alidg.me/blog/2019/6/21/tlab-jvm (文中的内存图片来源于此)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值