虚拟机在java堆中对象分配、布局和访问的过程
虚拟机在java堆中对象分配、布局和访问的过程
一、 对象的创建
从java程序,new指令开始
类加载
类加载通过后,内存分配
- 对象所需内存的大小在类加载完成后就可以完全确定,为对象分配空间的任务等于把一块确定大小的内存从Java堆中划分出来。
-
两种方法:
- 指针碰撞
假如,java堆中内存是绝对规整的,所有用过的内存和空闲的内存分为两部分,中间放一个指针作为分界点的指示器,那分配内存
- 空闲列表
假如,java堆中的内存并不是规整的,已使用的内存和空间的内存相互交错,如此虚拟机就必须维护一个列表,记录上哪些内存块是可用的,
- 分配方式的选择取决于:java堆是否规整;Java堆是否规整取决于:所采用的垃圾收集器是否带有压缩整理功能。
-
实例
- 在使用Serial、ParNew等带有Compact过程的收集器时,系统采用分配算法为:指针碰撞;
- 使用CMS,基于 Mark-Sweep算法的收集器时,系统采用分配算法为:空闲列表。
对象创建的线程安全问题
-
问题描述
对象创建在虚拟机中的行为非常频繁,即使只是修改一个指针所指向的位置,在并发情况下也并不是线程安全的,有可能出现正在给对象A分配
内存,指针还没有修改,对象B又同时使用了原来的指针来分配内存的情况。 -
解决方法,有两种:
- 1 对分配空间的动作进行同步处理
实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;
-
- CAS(比较与交换,Compare and swap)是一种有名的无锁算法。(ConcurrentHashMap也用到了此算法)
- 2 本地线程分配缓冲
把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲(Tlab)。
- 虚拟机是否使用Tlab,可以通过-XX:+/-UseTlab参数来设定
对象内存分配完成后,虚拟机把内存空间初始化
- 虚拟机将分配到的内存空间都初始化为零值(不包括对象头),如果使用Tlab,初始化操作可以提前至Tlab分配时进行。
-
目的:
保证了对象的实例字段在java代码中可以不赋初始值就可以直接使用,程序能访问到这些字段的数据类型所对应的的零值。
修饰对象
- 虚拟机对对象进行必要的设置。其实是对象头。
- 对象头中包括:对象的哈希码、对象的Gc分代年龄、此对象是哪个类的实例、类的元数据信息的指示等;
- 根据虚拟机当前的运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
从虚拟机的角度,一个新的对象已经产生。
从java程序的角度,对象创建才刚刚开始,init方法还没有执行,所有的字段都为零。
- 一般来说(由字节码中是否跟随invokespecial指令所决定),执行new 指令之后会接着执行方法,
把对象按照程序员的意愿进行初始化,这样一个真正可用的对象产生。
二、 对象的内存布局
对象在内存中布局分配分为三个部分
- 对象头
- 实例数据
- 对齐填充
1 对象头
- 如图:
-
第一部分
用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,
-
第二部分
类型指针,即对象指向它的类元素数据的指针,虚拟机通过该指针确定对象的实例是哪个类。
2 实例数据
- 对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。
- 包括父类继承的,子类定义的;
- 存儲順序: 虚拟机分配策略参数和字段在java源码中定义的顺序 ;
- hotSpot虚拟机默认的分配策略为longs/doubles、ints、 shorts/chars、bytes/booleans、oops(Ordinary Object Pointers),
相同宽度的字段被分配到一起。在这个前提下,父类中定义的变量会出现在子类之前。
3 对齐填充
- 不是必须存在的,只是占位符的作用。
- 因为对象的大小必须是8字节的整数倍。对象头部分正好是8字节的倍数,当对象实例数据部分没有对齐时,通过对齐填充补全。
三、 访问
访问方式有2个
- 句柄和指针
句柄访问
- 存储位置:java 堆中划分一块内存作为句柄池,reference中存储的就是对象的句柄地址;
- 包含信息:对象实例数据与类型数据各自的具体地址信息。
指针访问
- reference 存储的直接就是对象地址。
- 问题: 考虑java堆对象的布局中存放访问类型数据的相关信息。
优劣
句柄
- reference中存储的是稳定的句柄地址,对象被移动时,只改变句柄中的实例数据指针,而reference本身不需要修改。(在垃圾收集时移动对象时普遍的)
指针
- 速度更快,节省了一次指针定位的时间开销。--由于对象的访问在java非常频繁。(积少成多)
Sun HotSpot 采用第二种方式
参考: java虚拟机第二版
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
ArrayBlockingQueue 和LinkedBlockingQueue 代码解析(JDK8)
在使用线程池的时候,需要指定BlockingQueue 常用的一般有ArrayBlockingQueue和LinkedBlockingQueue有一天被问到有什么区别没回答上来,因此从代码的层面解析一下1 ArrayBlockingQueue顾名思义,就是用Array来实现的queue Blockqing 则说明是线程安全的 public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, Serializable { private static final long serialVersionUID = -817911632652898426L; final Object[] items; int takeIndex; int putIndex; int count; final ReentrantLock lock; private final Condition notEmpty; private final Condition n...
- 下一篇
Java线程池的增长过程
Java线程池的增长过程通过ThreadPoolExecutor的方式创建线程池ThreadPoolExecutor 构造方法: 复制代码public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { //code... }复制代码 参数的意义: 1.corePoolSize 指定了线程池里的线程数量,核心线程池大小2.maximumPoolSize 指定了线程池里的最大线程数量3.keepAliveTime 当线程池线程数量大于corePoolSize时候,多出来的空闲线程,多长时间会被销毁。4.unit 时间单位5.workQueue 任务队列,用于存放提交但是尚未被执行的任务。6.threadFactory 线程工厂,用于创建线程,一般可...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS6,CentOS7官方镜像安装Oracle11G
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Red5直播服务器,属于Java语言的直播服务器
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7