jvm-第三节jvm中的对象及引用

# jvm中的对象以及引用

问题

这篇文章主要探讨的几个问题:

  1. jvm中对象创建过程

  2. 对象的内存布局

  3. 对象的访问方式

  4. 如何判断对象是否存活

  5. 对象分配策略

  6. 四种引用的区别

jvm中对象的创建过程

  1. 检查加载:检查指令是否在一个常量池中定位到一个类的符号引用(一组符号描述所引用的目标),检查类是否加载解析初始化
  2. 分配内存:从jvm的堆中划分一块内存,这个划分有俩种情况,分别是指针碰撞和空闲列表
    1. 1687227244871指针碰撞:如果jvm堆中的空间是规整的,空闲一边,用过的一边,中间放一个指针,那么分配内存仅仅是吧指针挪动与对象大小等距距离;具体如图
    2. 1687227357553空闲列表:如果jvm堆种地空间不规整,那就需要维护一个空闲列表,分配时在空闲列表上找一块足够大的空间分配给对象,然后维护列表,具体如图
    3. 分配内存的并发安全问题:在多线程情况下分配内存,创建对象,是一个极其频繁的为题,t1线程创建对象分配内存修改指针没结束,t2就进来使用原指针分配内存,针对这个问题有俩种解决方式,一种是CAS,一种是分配缓冲TLAB
      1. 1687228415617CAS:通过cas操作,如果成功就分配内存,如图
      2. TLAB:是一种基于线程本地缓存方式,通过cas分配内存,每次分配时现在自己的TLAB中找,如果没有空间了就采用其他分配方式,比如直接分配;
      3. 分配缓冲:预先获取一块连续的内存,然后将缓冲池分成多块,每次分配对象就从缓冲池中拿一块进行分配;如果没有空间了,则会使用其他分方式,比如直接分配;
  3. 初始化零值:被分配内存空间的对象都要初始化零值,int =0 boolean =false这一步的意义是保证java对象在不赋值初始化的情况下可以使用
  4. 设置对象头:jvm对对象的必要设置,包括这个对象是哪个类的实例,如何找到类的元数据,对象hash ,gc,等信息,都在对象头中;具体看下面
  5. 初始化对象:对于jvm来说,一个对象已经创建完了;

对象的内存布局

1687230338210

  1. 在hotspot中对象的布局分为三类。分别是对象头,实例数据,对齐填充,分别介绍一下
    1. 对象头:存储的是运行的数据它包含三部分,存储对运行数据(markWord),类型指针,若是数组类型,有记录数组长度的记录
      1. 存储对象自身运行时数据包含 haah,gc分代年龄,锁标识,锁类型,偏向线程id,偏向时间戳
    2. 实例数据
    3. 对齐填充

对象的访问方式

  1. 句柄:如果采用句柄访问,jvm堆中会划分一块区域做句柄池,reference(在对象头中)中村的是句柄的地址,句柄中存的是对象实例和类型数据的地址
  2. 直接指针:reference中存的就是对象地址
  3. 对比:直接指针访问块,但是对象变更速度太快,所以一直修改开销大,句柄要二次访问慢,但是对面变更是句柄维护,reference中不需要修改。

判断对象是否存活的方式

  1. 堆中存放着几乎所有的对象,gc回收时要判断这些对象是否存活,如何判断存活,常见的有引用计数法,可达性分析

    1. 引用计数法:在对象中添加引用计数器,每当有一个地方引用就+1,失效时就-1,存在相互引用情况要引入额外基质处理,影响效率;
    2. 可达性分析:通过gc root作为起点,从这个节点开始向下搜素,沿途路径叫引用链,当一个对象到gc root没有任何引用链,则证明这对象不可用;
      1. 常见的gc root如下
        1. 虚拟机栈中引用的对象1687247376762
        2. 本地方法栈中引用的对象1687247438766
        3. 方法区静态属性引用的对象1687247487481
        4. jni引用的对象1687247517021
        5. 虚拟机内部引用的对象,如常量池引用的对象1687247604184
        6. jvm引用类型,软弱虚引用
        7. 根集合中的对象,在gc前预设好的对象
  2. Finalize 方法 (没啥用)

    即使通过可达性分析判断不可达的对象,也不是“非死不可”,它还会处于“缓刑”阶段,真正要宣告一个对象死亡,需要经过两次标记过程,一次是 没有找到与 GCRoots 的引用链,它将被第一次标记。随后进行一次筛选(如果对象覆盖了 finalize),我们可以在 finalize 中去拯救。

四种对象引用以及区别

  1. 强: Object obj = new Object(); // 强引用
  2. 软:SoftReference sr = new SoftReference<>(new Object()); // 软引用
  3. 弱: WeakReference wr = new WeakReference<>(new Object()); // 弱引用
  4. 虚: WeakReference wr = new WeakReference<>(new Object()); // 弱引用
  5. 他们之前的区别:那gc为例,
    1. 强引用-obj存在就不会回收
    2. 软引用-当内存不足时,会被回收
    3. 弱引用-下一次gc时就会被回收
    4. 虚引用-每逢gc必然回收,放入特殊队列

对象分配策略

  1. 除了常见的分配在堆中,还会分配到栈中,既逃逸下面详细说

  2. 对象的分配

    1. 栈上分配

      1. 首先说逃逸分析,分为俩种,一种是线程逃逸,既被多个线程访问的对象,另一种是方法逃逸,既被其他方法引用
      2. 如果对象没有逃逸,则在栈上分配对象,这样就会避免对象创建和回收;
      3. 举个例子,myobject属于不可逃逸,jvm在栈上分配1687250594911
    2. 堆中分配:

      1. 对象优先在堆中的eden:大多数情况下对象在eden上分配,空间不够就minor gc,这里会涉及道空间分配担保机制;

      2. 大对象直接进入老年代

      3. 长期存活的进入老年代:每一个对象有一个年龄计数器(在对象头的运行时数据区中的gc年龄),达到一定阈值就会进入老年代

posted @ 2023-06-20 16:55  小傻孩丶儿  阅读(44)  评论(0编辑  收藏  举报