1.取自尚硅谷大厂面试题第一季
尚硅谷经典Java面试题第一季(java面试精讲)
首先明确几个概念
- 子类初始化前,会先查看父类是否初始化过,如果没有则先初始化父类
- 每个类只会被初始化一次
- 类初始化的过程包括,按顺序执行类变量显示赋值和静态代码块执行
- 实例初始化过程包括,按顺序执行实例变量显示赋值和非静态代码块执行,构造方法最后执行
- 在子类构造方法中,会在构造方法第一行隐式调用父类无参构造方法
public static void main(String[] args){
Son s1 = new Son();
System.out.println();
Son s2 = new Son();
}
- 创建Son类实例,需要先初始化Son类,此时Father没有被初始化,所以先初始化父类,执行类变量 j 显示赋值,即执行Father.method(),打印 (5)
- 执行Father静态代码块,打印 (1)
- Father类初始化完成,进行Son类初始化,执行类变量 j 显示赋值,即执行Son.method(),打印 (10)
- 执行Son静态代码块,打印 (6)
- 创建Son实例,调用Son构造方法,此时隐式调用Father无参构造方法,进行Father实例初始化,执行实例变量 i 显示赋值,即执行this.method(),继承后,方法会被重写,此时this指向子类Son,所以打印 (9)
- 执行Father非静态代码块,打印 (3)
- 执行Father无参构造方法,打印 (2)
- Father实例初始化完成,进行Son实例初始化,执行实例变量 i 显示赋值,即执行this.method(),此时this指向子类Son,所以打印 (9)
- 执行Son非静态代码块,打印 (8)
- 执行Son指定参构造方法,打印 (7)
- sout 换行
- 再次创建Son实例,此时Son类和其父类Father类已经执行过类初始化,所以不会再次初始化,直接进行Son实例初始化,调用Son构造方法,此时隐式调用Father无参构造方法,进行Father实例初始化,执行实例变量 i 显示赋值,即执行this.method(),继承后,方法会被重写,此时this指向子类Son,所以打印 (9)
- 执行Father非静态代码块,打印 (3)
- 执行Father无参构造方法,打印 (2)
- Father实例初始化完成,进行Son实例初始化,执行实例变量 i 显示赋值,即执行this.method(),此时this指向子类Son,所以打印 (9)
- 执行Son非静态代码块,打印 (8)
- 执行Son指定参构造方法,打印 (7)
2.取自尚硅谷jvm全套课程
尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机)
通过第一题,应该能很快梳理出初始化过程
- 创建Son类实例,需要先初始化Son类,此时Father没有被初始化,所以先初始化父类,此时没有类变量显示赋值和静态代码块所以不处理
- 初始化Son类,此时没有类变量显示赋值和静态代码块所以不处理
- 创建Son类实例,调用Son构造方法,此时隐式调用Father无参构造方法,进行Father实例初始化,执行实例变量 x 显示赋值,继承后,属性不会被重写,所以父类实例赋值,依然会赋值给父类实例,对子类没有影响
- 执行Father无参构造方法,调用this.print(),继承后,方法会被重写,此时this指向子类Son,但是又因为子类实例没有初始化,所以子类实例变量没有显示赋值,只在分配实例内存空间的时候,给实例变量赋初值,即x = 0,所以打印 Son.x = 0
- 执行x = 20,此时父类实例x = 20
- Father实例初始化完成,进行Son实例初始化,执行实例变量 x 显示赋值,x = 30
- 执行Son指定构造方法,调用this.print(),继承后,方法会被重写,此时this指向子类Son,x已经显示赋值,所以打印 Son.x = 30
- 执行x = 40,此时子类实例x = 40
- 打印f.x,因为变量不存在重写,且引用变量声明为Father,即父类,所以打印父类属性,20
如果引用变量声明为Son,那么就会打印 40
3.简单总结类加载和实例创建过程
类加载过程
1.加载:
- 通过一个类的全限定名获取定义此类的二级制流文件
- 将这个字节流文件所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类各种数据的访问入口 2.链接:
- 验证:确保Class文件的字节流信息符合当前虚拟机要求,保证被加载类的正确性,不会危害JVM安全,主要包括四种验证,文件格式验证,元数据验证、字节码验证、符号引用验证
- 准备:为类变量分配内存并赋零值,final变量会进行显示初始化
- 解析:将常量池中的符号引用转换为直接以用 3.初始化:执行类构造器<clinit>()的过程,此方法不需要定义,是Java编译器自动收集类中的所有类变量的赋值动作和静态代码块的语言合并而来,其指令顺序按照源文件中语句出现的顺序执行,若该类有父类,JVM则保证在子类<clinit>()执行前,其所有父类的<clinit>()已执行完成,保证一个类的<clinit>()在多线程下被加同步锁,如果类中没有静态变量或者静态代码块,则不会创建<clinit>()
实例创建过程
- 判断对应类是否已经加载连接初始化:查看方法区中是否存在该类的符号引用,并判断是否已经加载连接初始化
- 为对象分配内存空间:根据实例变量大小,计算所需空间,然后在对中申请,根据不同的垃圾回收器,内存规整则指针碰撞,不规整则空闲列表的方式申请
- 处理并发问题:通过cas或者区域内存加锁的方式,保证内存空间申请的原子性,优先在TLAB中申请
- 初始化内存空间:为实例属性赋初值
- 设置对象头:将对象的hashcode,GC年龄信息,锁信息存储到对象头中
- 执行init方法,即构造方法