Java虚拟机-自动内存管理机制(Java内存区域与内存溢出异常---2HotSpot虚拟机对象)

上篇博客—> Java虚拟机-自动内存管理机制(Java内存区域与内存溢出异常—1.运行时数据区)
引言:基于实用优先的原则,这里使用HotSpot虚拟机和Java内存区域堆为例,探讨它们中对对象的分配,布局和访问的全过程

1.2HotSpot虚拟机对象探秘

1.2.1 对象的创建

前言:创建对象(例如克隆,反序列化)通常仅仅是一个new关键字,在虚拟机中,对象的创建是一个什么样的过程呢?

  • 虚拟机创建对象的过程:

1.虚拟机遇到一条new指令,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载,解析和初始化,如果没有,那就必须执行相应的类加载过程
2.在类加载检查完成后,虚拟机开始将为新生对象分配内存,对象所需的内存大小在类加载后便可以完全确定
3.当Java堆中内存是绝对规整的,所有用过的内存放一边,空闲的放另外一边,中间放着一个指针作为分界点的指示器,那么分配的内存大小就是那个指针向空闲那边挪动一段和对象大小相等的,这种分配方式为:“指针碰撞”,如果Java堆中的内存并不是规整的,此时虚拟机就需要维护一个列表,记录哪些内存块是可用的,在分配时从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种方式为:“空闲列表”
4.解决分配问题出现的问题,使用下面给的方式,一般采用TLAB
5.内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),如果使用TLAB,这一工作过程也可以提前到TLAB分配时进行,这一步是为了保证对象的实例字段在Java代码中可以不赋值初始值而可以直接使用.
6.虚拟机开始对对象进行必要的设置,例如这个对象是哪个类的实例,如何才可以找到类的元数据信息,对象的哈希码,对象的GC分代年龄等信息,这些信息存放在对象的对象头之中.

  • 关于指针碰撞和空闲列表使用问题:

由于Java堆是否规整由所采用的垃圾收集器是否带有压缩整理功能,所以这里具体使用哪种要看收集器

收集器类型分配内存方式
Serial,ParNew等带Compact收集器指针碰撞
CMS这种基于Mark-Sweep算法的收集器时空闲列表
  • 有关内存分配的问题

因为对象创建是十分频繁的,因此只是简单修改指针指向的位置,在并发情况下也并非线程安全的,可能出现正在给对象A分配内存,指针还没修改,此时对象B又使用了原来的指针分配内存,因此出现了两种解决办法:
1.对分配空间的动作进行同步处理—实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性
2.把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Alloction Buffer,TLAB).哪个线程需要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定

1.2.2 对象的内存布局

在HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:
1.对象头(Header)
2.实例数据(Instance Data)
3.对其填充(Padding)
接下来我我们详情学习一下这三块区域

对象头(Header)

  • 对象头包括两部分信息:
部分名称内容
存储对象自身的运行时数据 (在32位和64位虚拟机中分别为32bit和64bit因此又称为Mark World)哈希码,GC分代年龄,锁状态标识,线程持有的锁,偏向线程ID,偏向时间戳
类型指针对象指向它的类元数据的指针(虚拟机通过这个指针来确定这个对象是哪个类的实例)

实例数据(Instance Data)

  • 是对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容
  • 对于这些无论是父类继承还是子类中定义的,都要记录,记录的顺序受到虚拟机分配策略参数(FieldsAllocationStyle)和字段在java源码中定义的顺序影响.

对其填充(Padding)

  • 这部分内容不是必然存在,也没有什么特别的含义,它仅仅是占位符的作用.
  • 由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,也就是我们创建的对象大小必须是8字节的整数倍,因此使用对其填充来补全.

1.2.3 对象的访问定位

引言:对于这些虚拟机创建好的对象,java程序需要通过栈上的reference数据来操作堆上的具体对象.由于该类型在java虚拟机中规范中只规定了一个指向对象的引用,所以通过什么方式定位,访问具体位置并没有定义.

主流的访问方式:

  • 使用句柄
  • 直接指针

使用句柄

  • 方式:Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址
  • 图解:
    在这里插入图片描述

直接指针

  • 方式:Java堆对象的布局中就必须考虑如何设置访问类型数据的相关信息,而reference中存储的直接就是对象地址.
  • 图解:
    在这里插入图片描述

使用句柄和直接指针优势对比

使用方式优点
使用句柄可以使reference中存储的使稳定的句柄地址,在对象被移动时,只会改变句柄中的实例数据指针,而reference本身不需要修改
直接指针访问速度块,节省了时间,HotSpot虚拟机主要使用第二种方式
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值