目录
- 对象初始化顺序
- 几个理论依据
- 代码示例
- 结论
- 参考
- 虚拟机中对象的创建
- 分配内存方式
对象初始化顺序
几个理论依据
- 对象创建前,若类未加载,会进行类加载
- 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();
}
}
执行结果:
父类-执行-静态代码块
父类-执行-静态方法
子类-执行-静态代码块
子类-执行-静态方法
父类-执行-方法
父类-执行-方法块
父类-执行-构造
子类-执行-方法块
子类-执行-方法
子类-执行-构造
-----
父类-执行-方法
父类-执行-方法块
父类-执行-构造
子类-执行-方法块
子类-执行-方法
子类-执行-构造
结论
结论:
- 父类 按代码上下顺序执行 静态方法块和静态变量赋值语句
- 子类 按代码上下顺序执行 静态方法块和静态变量赋值语句
- 父类 按代码上下顺序执行 成员方法块和成员变量赋值
- 父类 执行 构造方法
- 子类 按代码上下顺序执行 成员方法块和成员变量赋值
- 子类 执行 构造方法
虚拟机中对象的创建
- 虚拟机遇到字节码new指令时,先检查能否在常量池中定位到类的符号引用,并检查该类是否被加载、解析、初始化过,若没有,则执行类加载过程,否则执行2
- 类加载检查通过后,虚拟机为新对象在分配内存(堆,小概率栈。涉及JIT、逃逸分析、垃圾收集等知识点)。对象所需内存大小在类加载完成后就可以确定。
- 对象初始化
- 栈的对象引用指向该空间地址
分配内存方式
- 指针碰撞:在内存规整的情况下,用指针将使用过的内存和未使用过的内存分开,在分配或释放内存时移动指针。(Serial、ParNew收集器)
- 空闲列表:在内存不规整的情况下,使用过的内存和未使用过的内存加错在一起,则虚拟机需要维护一个列表,记录哪块内存可用。(CMS)
JAVA堆是否规整由垃圾收集器是否能空间压缩整理决定。
指针碰撞可能带来的问题:
对象的创建较为频繁,在并发情况下,在给对象A分配内存时,指针还没有来得及修改,对象B又使用原来的指针分配内存。
解决方法:
- 对分配内存的动作同步处理,如CAS加失败重试
- 将内存分配的动作按照线程划分在不同的空间进行,即每个线程在Java堆中预分配一块内存,成为本地线程分配缓冲,一个线程需要分配内存时先在改线程内的缓冲区内分配,本地缓冲区使用光后,分配新的缓存区才同步锁定。