Java对象创建过程

Java对象创建流程如下步骤

image.png

判断是否加载类

当Java虚拟机执行一条new指令时,首先会检查这个指令的参数是否能在常量池中定位到类的符号引用,并且检查该类是否被加载、解析和初始化过。如果没有则执行加载过程。

为对象分配内存

对象所需的大小在类加载完成后便可以确定,为对象分配内存实际上就是把等同于一个确定大小的内存空间从Java堆中分配出来。

分配内存的两中方式
  • 指针碰撞

    要求Java堆中内存是绝对规整的,使用的内存和未使用的内存中间放一个指针作为分界点的指示器,分配内存仅仅是将指针向空间空闲方向挪动一段与对象大小相等的距离。

  • 空闲列表

    Java堆不是规则的,已使用的内存和未使用的内存是相互交错在一起的,此时虚拟机会维护一个列表,列表上记录那些内存快是可用的。此时分配内存时便是从列表中找到一块足够大的空间划分给对象实例,并更新列表。

解决分配内存的线程安全问题
  • CAS加上失败重试的方式保证更新的原子性
  • 本地线程分配缓冲(Thread Local Allocation Buffer, TLAB):每个线程在Java堆中预先分配一小块内存,在该线程的这一小块内存中分配内存,仅缓存区空间用完了后,才使用同步锁定分配新的缓冲区。

初始化零值

分配完内存后,对象会初始化零值。如果使用了TLAB的话,该过程可能会提前到TLAB分配内存时发生。

设置对象头

在分配完内存后会设置对象头,对象头中包括该对象是那个类的是咧,如何找到类的元数据信息,对象哈希码,GC分代年龄,是否启用偏向锁等信息。

执行<init>方法

此时对象已经创建完毕,但从Java程序的角度,对象才刚刚开始执行构造函数,此时Class文件中的<init>()方法还没有执行,所有字段都还是默认零值。仅执行<init>()方法后,对象安装程序员的意愿对对象进行初始化后,该对象才真正可用。

对象的内存分配

基本原则

  • 对象优先在Eden区进行分配
  • 大对象直接进入老年代
  • 长期存活的对象将进入老年代

动态对象年龄判定

HotSpot虚拟机中如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。

空间分配担保

在发生Minor GC之前, 虚拟机必须要检查老年代最大可用的连续空间是否大于新生代所有对象的空间,如果大于, 则此次Minor GC是安全的。如果不大于,则虚拟机会首先查看-XX:HandlePromotionFailure参数的设置值是否允许担保失败;如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,则尝试一次Minor GC;如果小于,或者-XX:HandlePromotionFailure 设置为不允许冒险,那么就会进行一次Full GC。

启动空间分配担保机制是有一定风险的。如果此次是Full GC并启用了空间分配担保机制,因为不清楚将要进入老年代对象的大小,仅能通过以往Full GC 进行评估, 有可能出现一种情况,实际上进入老年代的对象大小要大于老年的可用空间(极端情况是上次内存回收时新生代中所有对象都存活),这样就会出现内存溢出。

注意: 在JDK6 Update24之前,-XX:HandlePromotionFailure需要用户自己设置,之后,虽然虚拟机仍有这个参数,但实际上虚拟机不管有没有设置这个值,都会执行相对的规则:只要老年代的连续空间大于新生代对象总大小或历次晋升的平均大小,就会进行Minor GC,否则将进行Full GC。

逃逸分析(栈内分配)

  • 逃逸分析

    分析对象动态作用域,当一个对象在对象里面被定义后,如果被外部方法引用(列入传参),这是方法逃逸;如果本线程的对象被其它线程访问到,则是线程逃逸。从不逃逸、方法逃逸到线程逃逸被称为对象由低到高的不同逃逸程度。

  • 栈上分配

    一般应用中,完全不会逃逸的局部对象和不会逃逸出线程的对象便可以使用栈上分配,通过随着方法的结束销毁大量的对象可以降低垃圾收集子系统的压力。栈上分配可以支持方法分配,但不支持线程逃逸。

  • 标量替换

    标量: 若一个数据已经无法再分成更小的数据来表示(例如原始数据类型:int,long, reference等),那么这些数据被称为标量。

    聚合量: 如果一个数据可以继续分解,那么这个数据被称为聚合量。

    标量替换: 如果把一个Java对象拆散,根据程序访问的情况,将其用到的成员变量回复为原始类型来访问,整个过程被称为标量替换。

    如果逃逸分析证明一个对象不会被方法外部访问,并且这个对象可以被拆散,那么程序真正执行的时候将可能不创建这个对象,而是改为直接创建它的若干各被这个方法使用的成员变量来代替。标量替换可以看成栈上分配的一种特例,实现简单,但对逃逸分析要求高,它不允许对象逃逸出方法范围。

  • 命令
    开启逃逸分析:-XX:+DoEscapeAnalysis

    查看逃逸分析的分析结果: -XX:+PrintEscapeAnalysis

    开启标量替换:-XX:+EliminateAllocations

    查看标量替换情况: -XX:+PrintEliminateAllocations

对象内存分配流程图

image.png


WasteCode
56 声望1 粉丝

读书,听歌,写写代码


下一篇 »
JVM垃圾收集