new一个对象,java虚拟机做了什么?

时间:2022-12-28 10:31:43

这个问题一直困扰我很久,面试被问到好多次,但是都没有一个清晰的认识,然后最近在回头过来看下java基础回味下。

当刚写完一个java程序,main方法new一个对象,内存做了如下操作:
一、初期:
程序先进入编译时期,java编译器将程序代码编译成字节码信息;

二、中期—类加载和类连接

2.1类加载:首先通过类加载器类class文件,加载到内存方法区,并创建java.lang.class类对象

2.2类连接:
- 验证阶段:检验类的结构是否正确
- 准备阶段:对类的变量进行分配内存,并默认初始化
- 解析阶段:将二进制文件的符号引用(任何形式的字面值)解析为直接引用

三、后期(类和对象初始化)
1. 如果new对象有引用变量指向它,栈内存存放引用变量指向null对象(未初始化,默认为null);
2. new一个对象时,jvm会在堆内存中开辟一个空间存放该对象;
3. 创建对象时,对象的成员(变量)先进行默认初始化;基本类型为基本类型默认值,引用类型为null,即引用变量的引用地址存放在栈(stack)内存中,对象的成员变量及值存放在堆内存
4. 对象成员初始化,对栈内存中的成员变量指定值;
第一步显式初始化;
第二步构造代码块初始化;
5. 构造函数初始化,new是创建一个对象,使用构造函数进行初始化对象(默认有一个)。

注明:

  • 在中期时,类加载指的是静态成员变量和静态代码块;
  • 存在继承时:
    原则:先静后非,先父后子,先块后器
    执行顺序如下:
    第一步:父类静态成员变量(方法区)
    第二步:父类静态代码块(多个按照顺序执行)
    注意:根据静态代码块和变量位置顺序初始化变量
    第三步:子类静态成员变量(方法区)
    第四步:子类静态代码块
    第五步:父类成员变量和子类成员变量栈内存创建一片内存,指向值为null,先父类成员变量显式初始化(如果有的话)
    第六步:父类代码块(父类成员变量初始化
    第七步:父类构造器
    第八步:子类成员变量显式初始化(如果有的话)
    第九步:子类代码块(子类成员变量初始化
    第十步:子类构造器

例子分析:

class Base {
private String name="base" ;
public Base() {
tellName();
printName();
}
public void tellName() {
System.out.println("Base tell name: " + name);
}
public void printName() {
System.out.println("Base print name: " + name);
}
}
public class Dervied extends Base {
private String name = "dervied";
public Dervied() {
tellName();
printName();
}
public void tellName() {
System.out.println("Dervied tell name: " + name);
}
public void printName() {
System.out.println("Dervied print name: " + name);
}
public static void main(String[] args){
new Dervied();
}
}

打印结果为:

Dervied tell name: null
Dervied print name: null
Dervied tell name: dervied
Dervied print name: dervied

下来解释这程序过程:

  1. 声明父类成员变量name父=null,子类成员变量name=null;
  2. 显示初始化父类成员变量,name父=“base”;
  3. 执行父类构造器,执行tellName();方法,此处省略this关键字,实际是this.tellName(),this指当前对象Dervied子类,子类的name值还没有被初始化,所以默认为null,所以调用子类的tellName(),打印Dervied tell name: null,同理打印Dervied print name: null;
  4. 初始化子类成员变量name为dervied,所以打印
    Dervied tell name: dervied
    Dervied print name: dervied

这里单独分析类加载机制:APC

  1. All 全盘负责:当一个类加载器加载某个类时,全盘负责将其类依赖的类一并加载;
  2. Parent 父类委托:先父类加载器去试图加载该class,当父类加载器无法加载该class时,才从自己的类路径中加载该类;
  3. Cache 缓存机制:缓存机制会将所有曾经类加载器加载过的类存入缓存中,当程序中需要某个类先去缓存中查找,如果查不到,则系统会读取该类的class文件继续缓存。这样算是为什么每次修改类后要重启JVM的原因