jvm-第三节jvm中的对象及引用
# jvm中的对象以及引用
问题
这篇文章主要探讨的几个问题:
-
jvm中对象创建过程
-
对象的内存布局
-
对象的访问方式
-
如何判断对象是否存活
-
对象分配策略
-
四种引用的区别
jvm中对象的创建过程
- 检查加载:检查指令是否在一个常量池中定位到一个类的符号引用(一组符号描述所引用的目标),检查类是否加载解析初始化
- 分配内存:从jvm的堆中划分一块内存,这个划分有俩种情况,分别是指针碰撞和空闲列表
- 指针碰撞:如果jvm堆中的空间是规整的,空闲一边,用过的一边,中间放一个指针,那么分配内存仅仅是吧指针挪动与对象大小等距距离;具体如图
- 空闲列表:如果jvm堆种地空间不规整,那就需要维护一个空闲列表,分配时在空闲列表上找一块足够大的空间分配给对象,然后维护列表,具体如图
- 分配内存的并发安全问题:在多线程情况下分配内存,创建对象,是一个极其频繁的为题,t1线程创建对象分配内存修改指针没结束,t2就进来使用原指针分配内存,针对这个问题有俩种解决方式,一种是CAS,一种是分配缓冲TLAB
- CAS:通过cas操作,如果成功就分配内存,如图
- TLAB:是一种基于线程本地缓存方式,通过cas分配内存,每次分配时现在自己的TLAB中找,如果没有空间了就采用其他分配方式,比如直接分配;
- 分配缓冲:预先获取一块连续的内存,然后将缓冲池分成多块,每次分配对象就从缓冲池中拿一块进行分配;如果没有空间了,则会使用其他分方式,比如直接分配;
- 初始化零值:被分配内存空间的对象都要初始化零值,int =0 boolean =false这一步的意义是保证java对象在不赋值初始化的情况下可以使用
- 设置对象头:jvm对对象的必要设置,包括这个对象是哪个类的实例,如何找到类的元数据,对象hash ,gc,等信息,都在对象头中;具体看下面
- 初始化对象:对于jvm来说,一个对象已经创建完了;
对象的内存布局
- 在hotspot中对象的布局分为三类。分别是对象头,实例数据,对齐填充,分别介绍一下
- 对象头:存储的是运行的数据它包含三部分,存储对运行数据(markWord),类型指针,若是数组类型,有记录数组长度的记录
- 存储对象自身运行时数据包含 haah,gc分代年龄,锁标识,锁类型,偏向线程id,偏向时间戳
- 实例数据
- 对齐填充
- 对象头:存储的是运行的数据它包含三部分,存储对运行数据(markWord),类型指针,若是数组类型,有记录数组长度的记录
对象的访问方式
- 句柄:如果采用句柄访问,jvm堆中会划分一块区域做句柄池,reference(在对象头中)中村的是句柄的地址,句柄中存的是对象实例和类型数据的地址
- 直接指针:reference中存的就是对象地址
- 对比:直接指针访问块,但是对象变更速度太快,所以一直修改开销大,句柄要二次访问慢,但是对面变更是句柄维护,reference中不需要修改。
判断对象是否存活的方式
-
堆中存放着几乎所有的对象,gc回收时要判断这些对象是否存活,如何判断存活,常见的有引用计数法,可达性分析
- 引用计数法:在对象中添加引用计数器,每当有一个地方引用就+1,失效时就-1,存在相互引用情况要引入额外基质处理,影响效率;
- 可达性分析:通过gc root作为起点,从这个节点开始向下搜素,沿途路径叫引用链,当一个对象到gc root没有任何引用链,则证明这对象不可用;
- 常见的gc root如下
- 虚拟机栈中引用的对象
- 本地方法栈中引用的对象
- 方法区静态属性引用的对象
- jni引用的对象
- 虚拟机内部引用的对象,如常量池引用的对象
- jvm引用类型,软弱虚引用
- 根集合中的对象,在gc前预设好的对象
- 常见的gc root如下
-
Finalize 方法 (没啥用)
即使通过可达性分析判断不可达的对象,也不是“非死不可”,它还会处于“缓刑”阶段,真正要宣告一个对象死亡,需要经过两次标记过程,一次是 没有找到与 GCRoots 的引用链,它将被第一次标记。随后进行一次筛选(如果对象覆盖了 finalize),我们可以在 finalize 中去拯救。
四种对象引用以及区别
- 强: Object obj = new Object(); // 强引用
- 软:SoftReference
不恋尘世浮华,不写红尘纷扰