对象一定分配在堆上吗?

      读过之前半文章的小哥哥们肯定都知道,宝宝我最近入坑了Golang,当然这不是自愿的也说不上胁迫,反正就入坑了,没想到21世纪的C语言也有了堆和栈,随之而来的战利品是逃逸分析,自然这只是其中之一,其他的离题比较远,写文章最主要的是什么?不被催稿(奋斗状)!这个我有生之年是做不到了

      还是先说说对象的分配吧,毕竟起个名字不容易,再离题了多尴尬~

      在众多图当中发现一个比较喜欢的,分享一下,当然最最感谢的是作者:爱谁 谁,开个玩笑,斯武、风晴大哥

     当然这个图也没涵盖所有点,比如这个逃逸分析是需要开启的-XX:+DoEscapeAnalysis,好像1.8是默认开启的,开了也是有些代价的,这需要权衡,开与不开——大佬您决定(下面会写);这个tlab也是一个点Thread Local Allocation Buffer线程本地分配缓存,他在伊甸园内 默认开启、默认设定为Eden Space的1%,见名猜意他是线程私有、独占的,因为独占所以不存在锁的问题、(自己跟自己抢锁玩~是不是闲的),自然也少了锁的开销,也不需要线程间同步,是不是优秀如大佬;思来想去、具体的过程还是说一下,因为顶重要的

  1. 逃逸分析确定在栈还是堆上,如是堆则
  2. 如tlab_top+size <= tlab_end则在TLAB上分配其增加tlab_top的值
  3. 如>则重新申请tlab,尝试存放
  4. 放不下则Eden加锁且eden_top+size<=eden_end则放Eden,增eden_top
  5. 如放不下则YoungGC,此时eden还是不足,么办法分代担保——老年代上

 上面的过程涉及到jvm的选择策略,默认利用refill_waste值进行比较,大于则堆上分配否则废弃当前TLAB新建tlab分配新对象

 堆上对象分配可利用

  • 指针碰撞:serial、parNew等compact的收集器,内存是规整的,使用的和闲的之间有指针分界,分配内存时移动指针进行分配,bump the pointer
  • 空闲列表:CMS基于mark-sweep的收集器,内存不规整肿么办?空闲列表帮忙您!谁来维护这个列表?当然时虚拟机老大哥了,大哥会记录哪些可用,分配时从列表中找足够大的进行分配就ok了

这当中隐晦地设计到大对象这个问题,那几个比较不就是嘛,这个大对象可指定 收集器的情况也不是很一样,情况吧稍微复杂些,但是搜或者查都不难找到,所以我就不写了

      官方回答逃逸分析:一种确定指针动态范围的方法,就是在程序的哪些地方可访问到这些指针,具体涉及到指针分析和形状分析,是不是不好理解,在次要特别鸣谢公司让贫下中农的我可以”科学上网“;再直白一些:逃逸分析确定某个指针可以存储的所有地方,以及确定能否保证指针的生命周期只在当前进程或在其它线程中。

       以好理解的角度说:一个函数分配了一个对象并返回该对象的指针,那么这个对象是不是就可能被其他方法访问到,”可能“这两字能让我们确定它的活动区域吗?显然不能 看这不就在你眼皮底下逃逸了;无独有偶如果这个指针存在全局变量或其他数据结构中,这些地方是不是都是有可能被共享的地方(一丁点可能性也算 不放弃不抛弃是我们的宗旨),共享是确定的但是不一定共享到哪里取,说白了就是它'作妖范围'无法确定,只能放任其到堆中遨游;简单说就是这个对象只在方法内,方法结束对象释放,当然这要开启逃逸分析和标量替换

      有三种逃逸状态:

  • 全局:一个对象的引用逃出了方法或线程:类变量为对象的引用,存在于一个逃逸的对象中,对象引用作为返回值 返给了调用方
  • 参数级:方法调用过程中传递对象引用给方法,这句话明明很简单这么写书面气增加了不少
  • 无:可进行标量替换(下面会讲)

说到这里就不得不说JIT了,又是一块带肉的骨头;篇幅太长,链接献上

写了那么多,为什么要逃逸分析,那可能是有很多好处的,本来不想写但是有一个小关键不写一点撑不起来这篇文章:

       对象分离高大上一些也可以叫:标量替换,问把大象装冰箱……离题了,跑出来,扪心自问分配的时候真的是把对象完完整整的分配到栈上了吗?那肯定不是,那不明显违反了栈的初衷,寄人篱下就要有低人一等的融入姿态,实际上是把对象分解成一个个的标量,互相之间呐偷偷地联系着,这样就不用对象头了,锁就更不需要了,GC少了,内存的回收效率也高了,这么想想得到的快乐 也没有画的饼那么大……开启标量替换-XX:+EliminateAllocations,当然要在开启逃逸分析的基础上,1.8是默认开启的

      上面有个要敲黑板的点:标量,首先应该get到一个信息:标量不可再分,谁不可再分?基数数据和对象引用、完美!那对应的能分解的是什么?对象,书面称呼是:聚合量,这个就不解释了

 

最近敖丙发生了有些事情,我不是偏袒他,大家可能会抗议:完了、这话一说完,就要开始偏袒了,其实还好,定位不一样、内容自然不一样,像语文考试一样,离题的作文分能高吗?别整那些剑走偏锋的场景,好好走路,我是谢谢熬丙的面试文章分享,同时希望他保持初心、少水文,当然这也是我对自己的要求,谢谢、说了一些胡话,不到之处、小哥哥小姐姐请见谅,爱你们♥

 

源码对象分配: https://juejin.im/post/6844903876194205710 c++ 挺详细的

 

对象创建方式

  • User user= new User();//首先方法区常量池查是否有类名符号引用、类加载信息:被加载和解析过,无则加载类,实例分配内存,调用构造函数,初始化成员字段,对象指向分配的内存空间,非原子操作,可能先指向空间再调构造函数
  • object类的clone:jvm创建新对象,可用看成是深度拷贝(想起了go的拷贝);这个不会调构造函数,是内存中二进制流进行拷贝的,重新分配内存块
  • 反序列化:实现Serializable接口然后借助流,涉及到IO 有些开销的,要注意;parcelable是android内存序列化支持
  • 反射:拿到类对象Class,通过反射创建类实例对象

         获取Class三种方式:类.class ,对象.getClass,Class.forName("类全路径")

         获实例对象两种方式:

             Class.newInstance(将调用无参构造函数)

             调用类对象构造方法:

                 getConstructors获取对象所有可见构造函数

                 getConstructors(Class…paramTypes)获取指定的构造函数

  • 使用unsafe:只要类对象即可unsafe.allocateInstance(ClassName.class);

接下来分配内存,这个上面也写了这里也是结尾处所以不写了

    内存分配好了之后初始化为零值(go也有零值),之后存信息,比如这个对象是哪个类的,怎样获取元数据、hashcode、GC分代年龄等,是的、这些在对象头中;

    init方法完成对象初始化,这样就建了一个对象

其实这个粒度还是有点粗的,或者说近底层了一些,本来就是底层的knowledge,java层面或着说spring的……貌似又要欠下几篇文章了,惭愧~

对象访问方式

句柄            

 堆中句柄池,栈中引用存储句柄地址,句柄存储对象实例数据和类型数据:下面这个图很常见了、是吧

直接指针

这两个的优缺还是很明显,简单说对于句柄,栈引用的是地址,对象移动时只修改句柄地址即可,但是两次指针定位、比较慢;对于指针,一次定位即可访问速度快,当然这也是他的缺点,同时需要考虑如何存放类型数据等信息,指针时HotSpot的选择

 

参考:

https://juejin.im/post/6844903905323646990 创建类

《深入理解Java虚拟机:JVM高级特性与最佳实践》

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值