目录
如何创建对象
对象内存如何分配
如何确认内存是否规整
内存分配线程安全问题如何规避解决
对象内存是如何布局的
对象在内存中如何寻址
如何判断对象已死
可以作为GCRoot的对象有
如何创建对象
new 类名()
1 类加载检查 检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载,解析和初始化过,如果没有,那必须先执行相应的类的加载过程
2 为对象分配内存 对象所需内存的大小在类加载完成后便完全确定,等同于把一块确定大小的内存从java堆中划分出来
3 内存空间初始化 虚拟机将分配到的内存空间都初始化为零值(不包含对象头) 保证了对象的实例字段在java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值
4 对象设置 虚拟机对对象进行必要的设置,例如这个对象是哪个类的实例,如果才能找到类的元数据信息,对象的哈希码,对象的GC分代年龄等信息,这些信息存放在对象的对象头之中
5 初始化 init 程序员指定的初始化方法
对象内存如何分配
指针碰撞(内存规整) 已使用和空闲的内存分两边,中间放着一个指针作为分界点的指示器,分配内存仅仅是把指针向空闲空间那边挪动一段与对象相等的距离
空闲列表(内存不规整)虚拟机维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录
如何确认内存是否规整
根据GC收集器是否带有压缩功能决定
指针碰撞 Serial,PerNew等带Compact过程的收集器
空闲列表 CMS这种基于Mark-Sweep算法的收集器
内存分配线程安全问题如何规避解决
同步处理 JVM采用CAS机器加上失败重试的方式,保证更新操作的原子性
本地线程分配缓冲区(TLAB) 内存分配是线程安全是如何解决的 把内存分配的动作按照线程划分在不同的空间中进行,每个线程在Java堆中预先分配一小块内存(TLAB)哪个线程需要分配内存就从哪个线程的Tlab上分配,只有Tlab用完需要分配新的Tlab时,才需要同步处理 (通过”-XX:+/-UseTALAB”指定JVM是否使用TLAB)
对象内存是如何布局的
1 对象头
1.1 存储对象自身的运行时数据 Mark Word(在32bit和64bit虚拟机上长度分别为32bit和64bit)
1.1.1 对象hashCode
1.1.2 对象GC分代年龄
1.1.3 锁状态标识(轻量级锁,重量级锁)
1.1.4 线程持有的锁(轻量级锁,重量级锁)
1.1.5 偏向锁相关:偏向锁,自旋锁,轻量级锁以及其他一些锁优化(在Synchronized可以满足的情况下,优先使用) 除非是使用一些显示锁独有的功能,例如指定时间等待等
1.2 类型指针 对象指向类元数据的指针,JVM通过这个指针来确定这个对象是哪个类的实例 (32bit->32bit, 64bit->64bit(未开启压缩指针), 32bit(开启压缩指针))
2 实例数据 对象真正存储的有效信息
3 对齐填充 JVM要求对象的大小必须是8的整数倍,若不是,需要补位对齐
4 数据类型及包装类的大小 boolean,byte (1bytes) short,char(2bytes) int,float(4bytes) long,double(8bytes) refrences(32bit系统占用6bytes,64bit系统占用8bytes)
对象内存是如何布局的
句柄访问 堆中划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,句柄中包含了对象实例数据与类型数据各自的具体地址信息
直接指针 reference存储的直接就是对象地址
如何判断对象已死
引用计数算法 给对象添加一个引用计数器,每当有一个地方引用它时,计数器加1;当引用失效时,计数器值减1;任何时刻计数器为0的对象就是不可能再被使用
1.1 优点 实现简单
1.2 缺点 无法解决两个对象相互引用的问题
可达性分析算法 通过一系列称为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的
可以作为GCRoot的对象有
虚拟机栈(栈帧中的本地变量表)中引用的对象(方法中对象)
方法区中类静态属性引用的对象
方法区中常量引用的对象