以下内容来自网络整理,侵删
Student s = new Student(); 在内存中做了哪些事?
- 加载Student.class 文件进内存
- 在栈内存为s开辟空间
- 为学生对象分配内存
- 初始化分配到的空间,成员变量设置默认值
- 设置对象头信息
- 执行构造方法对学生对象的成员变量赋值
- 学生对象初始化完毕,把对象地址赋值给s变量
对象的四种创建方式
- 类加载检查。判断这个对象对应的类是否已经加载、解析、初始化过。
- 为对象分配内存
- 如果内存规整,那么虚拟机将采取指针碰撞法(把指针向空闲空间那边挪动一段与对象大小相等的距离。)来为对象分配内存。
(像Serial,ParNew这种基于标记-整理算法的垃圾收集器,虚拟机采用这种方式分配内存) - 如果内存不规整,虚拟机将采用空闲列表法为对象分配内存
(使用标记-清除算法的CMS垃圾收集器) - 并发安全:方式1,采用CAS失败重试、区域加锁保证更新的原子性;方式2,内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)
(意思是虚拟机维护了一个列表,记录哪些内存块是可用的,再分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表的内容)
- 如果内存规整,那么虚拟机将采取指针碰撞法(把指针向空闲空间那边挪动一段与对象大小相等的距离。)来为对象分配内存。
- 初始化分配到的空间,所有属性初始化为零值,保证对象实例字段在不赋值时可以直接使用
- 设置对象头信息
- 执行构造方法进行初始化
对象什么时候不在堆中分配
- 逃逸: 逃逸是指在某个方法之内创建的对象,还在方法体之外被其它变量引用到;这样带来的后果是在该方法执行完毕之后,该方法中创建的对象将无法被GC回收。比如有方法逃逸、线程逃逸。
- 开启逃逸分析后,如果对象的作用域仅在方法内,那么对象可能创建在虚拟机栈上面,随方法入栈创建,出栈销毁,减小垃圾回收压力
对象的内存布局
包含三部分:对象头(header),实例数据(instance data)、对齐填充(padding)
- 对象头。包含两部分:运行时数据、类型指针
- Mark Word(标记字段)。包括:哈希吗(hashcode)、GC分代年龄、锁状态标志、线程持有锁、偏向线程ID、偏向时间戳(这部分与synchronized底层实现原理有关)
- 类元数据的指针,通过这个指针可以知道,对象是哪个类的实例。如果是数组,需要记录数组长度。注意:访问通过直接指针才会这样。句柄访问,不会有类型指针。
- 实例数据
- 存储对象的真正的数据。父类中的数据也会记录在这里。相同宽度的字段优先分配到一起。
- 对齐填充。
- 保证对象的大小是8字节的整数倍,占位符的作用
对象的访问定位
两种方法:句柄、直接指针
- 句柄。Java堆中对应一块内存—句柄池。对象的引用指向句柄池,句柄指向堆中的实例和方法区中的类数据。
- 直接指针。对象的引用直接指向了堆中的实例。实例里面存储了类数据
- 句柄优点:移动对象不需要修改对象的引用,只需要修改句柄的内容。
- 缺点:需要两次定位
- 直接指针优点:快,只需要一次定位。
- 缺点:对象移动,需要修改对象的引用。
- Hotspot虚拟机实现,默认使用直接指针。