Java对象分配


对象的创建过程

判断对象对应的类是否加载、链接、初始化

  1. 虚拟机遇到一条new指令,首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载,解析和初始化。(即判断类元信息是否存在)
  2. 如果没有,那么在双亲委派模式下,使用当前类加载器以ClassLoader + 包名 + 类名为key进行查找对应的 .class文件
  3. 果没有找到文件,则抛出ClassNotFoundException异常,
  4. 如果找到,则进行类加载,并生成对应的Class对象。

对象内存分配过程

在分配之前,首先先会计算对象占用空间的大小,接着在堆中划分一块内存给新对象。(如果允许使用栈上分配,并且符合栈上分配的条件,则在栈上进行分配。)

  • 如果内存规整,则使用指针碰撞法。指针碰撞法意思就是放一个指针作为分界点,使用过的内存放在一边,空闲的放在另外一边。分配内存时调整指针的位置即可。
  • 如果内存不规整,则需要维护一个空闲列表记录可用的内存块来为对象分配内存。

使用哪种方式是由堆是否规整所决定,而是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

Java对象分配过程

对象在栈上分配的过程

基本思想:将线程私有的对象打散分配在栈上
栈上分配是基于逃逸分析和标量替换实现的。逃逸分析的目的是判断对象的作用域是否有可能逃逸出函数体。标量替换是将聚合对象(非原始数据类型),就会把这个对象拆解成若干个其中包含的若干个成员变量来代替。
比如

class Point {
    private int x;
    private int y;
}
private static void alloc() {
    Point point = new Point(1,2);
    System.out.println("point.x" + point.x + ";point.y" + point.y);
}

会被替换成

private static void alloc() {
    int x = 1;
    int y = 2;
    System.out.println("point.x = " + x + "; point.y=" + y);
}

对象在TLAB分配的过程

TLAB分配过程
TLAB是从内存模型的角度,为Eden区继续划分,JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内。
多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,同时还能够提升内存分配的吞吐量,因此我们可以将这种内存分配方式称之为快速分配策略。

对象首先是通过TLAB开辟空间,如果不能放入,那么需要通过Eden来进行分配

若要分配的对象在TLAB中不足有多余空间分配,那么会有两种分配策略

  • 废弃当前的TLAB
  • 将这个30KB的对象直接分配到堆上,保留当前TLAB

对象在堆上的分配过程

  1. new的对象先放Eden园区。此区有大小限制。
  2. 当Eden园的空间填满时,程序又需要创建对象,JVM的垃圾回收器将对Eden园区进行垃圾回收(MinorGC),将Eden园区中的不再被其他对象所引用的对象进行销毁。再加载新的对象放到Eden园区
  3. 然后将Eden园中的剩余对象移动到Survivorv0区。
  4. 如果再次触发垃圾回收,此时上次幸存下来的放到Survivorv0区的,如果没有回收,就会放到Survivorv1区。
  5. 如果再次经历垃圾回收,此时会重新放回Survivorv0区,接着再去Survivorv1区。
  6. 啥时候能去养老区呢?可以设置次数。默认是15次。或者是Survivor慢后,将会触发一些规则,直接去到老年区
  7. 在养老区,相对悠闲。当养老区内存不足时,再次触发GC:Major GC,进行养老区的内存清理
  8. 若养老区执行了Major GC之后,发现依然无法进行对象的保存,就会产生OOM异常。

对象在堆上的分配过程

设置对象头

将对象的所属类(即类的元数据信息)、对象的HashCode和对象的GC信息、锁信息等数据存储在对象的对象头中。这个过程的具体设置方式取决于JVM实现。

对象头包含了两部分,分别是 运行时元数据(Mark Word)和 类型指针

初始化

执行init方法进行初始化。在Java程序的视角看来,初始化才正式开始。初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量

Java对象创建过程


文章作者: 彭峰
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 彭峰 !
  目录