目录

  • 对象初始化顺序
  • 几个理论依据
  • 代码示例
  • 结论
  • 参考
  • 虚拟机中对象的创建
  • 分配内存方式


对象初始化顺序

几个理论依据

  • 对象创建前,若类未加载,会进行类加载
  • static final 常量在类加载的准备阶段就完成初始化
  • 对象创建时,会先创建父类对象
  • 代码编译后会在字节码文件中生成
  • 实例构造器<init>方法,将所有的成员代码块和成员变量赋值动作按顺序收集在一起,在对象实例化中执行
  • 类构造器<cinit>方法,将所有的静态代码块和静态变量赋值动作按顺序收集在一起,在类加载过程中执行

代码示例

父类Parent

public class Parent {
    static {
        System.out.println("父类-执行-静态代码块");
    }

    static String staticA = getStaticA();

    public static String getStaticA() {
        System.out.println("父类-执行-静态方法");
        return null;
    }

    String b =  getPA();


    {
        System.out.println("父类-执行-方法块");
    }
    public String getPA() {
        System.out.println("父类-执行-方法");
        return null;
    }
    public Parent(){
        System.out.println("父类-执行-构造");

    }
}

子类Children

public class Children extends Parent{
    static {
        System.out.println("子类-执行-静态代码块");
    }

    {
        System.out.println("子类-执行-方法块");
    }
    static String staticC = getStaticA();

    public static String getStaticA() {
        System.out.println("子类-执行-静态方法");
        return null;
    }


    String c =  getA();

    public String getA() {
        System.out.println("子类-执行-方法");
        return null;
    }

    public Children(){
        System.out.println("子类-执行-构造");
    }
}

测试代码:

public class Test {
    public static void main(String[] args) {
        new Children();
        System.out.println("-----");
        // 第二次创建对象时,类已经加载过,不需要再加载,也不会执行静态方法和代码块
        new Children();
    }
}

执行结果:

父类-执行-静态代码块
父类-执行-静态方法
子类-执行-静态代码块
子类-执行-静态方法
父类-执行-方法
父类-执行-方法块
父类-执行-构造
子类-执行-方法块
子类-执行-方法
子类-执行-构造
-----
父类-执行-方法
父类-执行-方法块
父类-执行-构造
子类-执行-方法块
子类-执行-方法
子类-执行-构造

结论

结论:

  • 父类 按代码上下顺序执行 静态方法块和静态变量赋值语句
  • 子类 按代码上下顺序执行 静态方法块和静态变量赋值语句
  • 父类 按代码上下顺序执行 成员方法块和成员变量赋值
  • 父类 执行 构造方法
  • 子类 按代码上下顺序执行 成员方法块和成员变量赋值
  • 子类 执行 构造方法

虚拟机中对象的创建

  1. 虚拟机遇到字节码new指令时,先检查能否在常量池中定位到类的符号引用,并检查该类是否被加载、解析、初始化过,若没有,则执行类加载过程,否则执行2
  2. 类加载检查通过后,虚拟机为新对象在分配内存(堆,小概率栈。涉及JIT、逃逸分析、垃圾收集等知识点)。对象所需内存大小在类加载完成后就可以确定。
  3. 对象初始化
  4. 栈的对象引用指向该空间地址

分配内存方式

  • 指针碰撞:在内存规整的情况下,用指针将使用过的内存和未使用过的内存分开,在分配或释放内存时移动指针。(Serial、ParNew收集器)
  • 空闲列表:在内存不规整的情况下,使用过的内存和未使用过的内存加错在一起,则虚拟机需要维护一个列表,记录哪块内存可用。(CMS)
    JAVA堆是否规整由垃圾收集器是否能空间压缩整理决定。

指针碰撞可能带来的问题:

对象的创建较为频繁,在并发情况下,在给对象A分配内存时,指针还没有来得及修改,对象B又使用原来的指针分配内存。

解决方法:

  • 对分配内存的动作同步处理,如CAS加失败重试
  • 将内存分配的动作按照线程划分在不同的空间进行,即每个线程在Java堆中预分配一块内存,成为本地线程分配缓冲,一个线程需要分配内存时先在改线程内的缓冲区内分配,本地缓冲区使用光后,分配新的缓存区才同步锁定。