对象的生命周期

实例化对象的方式

  • 明确使用new操作符
  • 调用Class或是Constructor对象的newInstance方法
  • 调用现有对象的clone方法
  • 通过ObjectInputStream类的getObject方法反序列化

实例化对象的过程

  • 在堆中为对象分配内存
  • 将实例变量初始化为默认值
    • 如果使用了TLAB,这一项也可提前至TLAB分配时进行。
  • 设置对象头
    • 注意对象的hash码是到真正使用时Object::hashCode()才会调用
  • 为实例变量赋予正确的初始值,执行<init>方法
  • 对象终结,被垃圾收集器回收
    • 如果类声明了一个finalize终结方法,垃圾收集器会在释放这个实例占据的内存前执行这个方法。

为对象分配内存

对象所需的内存大小在类加载完毕后便可以确定,

为对象分配空间实际上等同于把一块确定大小的内存从Java堆中划分出来。

  • 指针碰撞(Bump The Poiner)

假设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的放在另一边,

中间使用一个指针作为分界点的指示器,那分配内存就只是将指针往空闲空间方向移动

和对象大小相等的距离即可。

  • 空闲列表(Free List)

若已使用和未使用的内存交错在一起,虚拟机就必须维护一个列表,记录哪些内存块是

可用的。分配内存时先查找列表,找到一块空间分给对象,然后更新列表。

分配方式的选择

实际上取决于堆是否规则,堆是否规则取决于使用的垃圾收集器是否具有空间压缩整理(Compact)能力。

SerialParNew。使用了这些虚拟机,分配的方式将是指针碰撞,否则理论上只能是空闲列表。

分配空间时的并发处理

对象创建在虚拟机中是非常频繁的行为,即使仅仅修改一个指针所指向的位置,在并发情况下也是不安全的。

两种解决方案:

  • 对分配内存空间的动作进行同步处理(实际上虚拟机是采用CAS配上失败重试的方式保证更新操作的原子性)

  • 把内存分配的动作按照线程划分在不同的空间进行,即每个线程在Java堆中预先分配一小块内存,

    称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB),哪个线程需要分配内存,就在哪个

    线程的本地缓存区中分配,只有本地缓存区用完了,分配完才需要同步锁定。

    虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来设定。


为对象赋值
  • 如果通过clone方法,则将被克隆的实例的值拷贝到新的实例
  • 如果通过ObjectInputStreamreadObject方法,则通过流中读入的值来初始化
  • 否则,调用对象的实例初始化方法<init>

Java编译器为它编译的每一个类至少生成一个实例初始化方法,在Java的class文件中,这个实例初始化方法称为<init>。针对一个类的构造方法,Java编译器都产生一个<init>方法。若不声明任何构造方法,则会默认产生一个无参数的构造方法。


finalize

在进行垃圾回收时,如果被回收的对象覆盖了finalize()方法,那么该对象会被放置在一个名为F-Queue的队列中,

并在稍后由一条虚拟机自动创建、低调度优先级的Finalizer线程去执行它们的finalize()方法。

finalize()中对象可以拯救自己,只要与引用链上的任何一个对象建立关联即可(可达性分析算法的情况),

不过finalize()方法只会触发一次。

jmap -finalizerinfo vmid 可以显示F-Queue中等待Finalizer线程执行finalize()方法的对象。