JVM工作原理-类成员的加载和执行过程
jvm可以执行字节码文件,当我们通过jvm执行一个java字节码文件时,jvm首先在内存中开辟一块jvm内存,专门用于执行java字节码文件,另外jvm会通过一个名为类加载器(Class Loader)这么一个角色将电脑硬盘上字节码文件加载到jvm内存中,然后在jvm内存中执行字节码文件中记录的信息(程序)。此时我们编写的java源文件才真正的在内存中运行起来。
注意:jvm内存就是我们经常说的栈内存、堆内存、方法区(常量池、方法区、静态元素区)的一个总称。
如下图:
现在我们知道了在我们通过java.exe执行工具执行字节码文件时,jvm会自动的将这个字节码文件通过类加载器Class Loader将字节码文件加载到jvm内存空间中的方法区中,那么这个字节码文件是原封不动的从硬盘搬到内存中吗?字节码文件中属性和方法是怎么个加载过程?这些问题我们继续探讨。
类成员的加载和执行过程
字节码文件中包含的信息有:类名、类成员(属性、方法)。那么研究字节码文件的执行过程无非就是研究字节码文件中的类成员的加载和执行过程。我们知道一个类的类成员大致可以分为属性和方法,但是细分可以分为一般属性、一般方法、构造代码块、构造方法、静态属性、静态方法、静态代码块,那么接下来的探讨可以分为三个部分:
- 属性和方法的加载位置。
- 属性和方法的加载顺序。
- 构造方法、构造代码块、静态代码块的执行顺序。
属性和方法加载位置:
一般属性、一般方法、构造代码块、构造方法加载到方法区中,而静态属性、静态方法、静态代码块则存储在方法区中静态元素区中。如下图:
属性和方法的加载顺序:
依次加载:一般属性、一般方法、构造代码块、构造方法、静态元素、静态方法、静态代码块。
如下图:
当我们通过new的方式创建一个Person对象时,就会在堆内存中开辟一块内存空间,将Person类模板上的一般属性加载到这块内存上,并新增了一个this属性用于指向这块内存自己本身,此外这块内存空间保存着其对应的类。如下图:
注意:这个图还存在一些瑕疵,因为这个内存图没有暂时还没有考虑到Person类的父类的加载过程。
构造方法、构造代码块、静态代码块的执行顺序:
当我们通过new的方式创建一个Person对象时,需要调用Person类的构造方法,在调用构造方法之前,我们需要将Person.class字节码文件加载到jvm内存中的方法区中,此时我们才能找到Person类的构造方法并调用,但是需要注意的是,此时Person中的属性和方法都必须加载到方法区中才能访问,而静态代码块一旦加载完毕之后就会立即执行,所以在调用构造方法之前,静态代码块就已经执行一次且仅一次。当静态代码块执行完毕之后,我们才能调用构造方法,而在调用构造方法之前,构造代码块需要执行一遍。
综上:静态代码块先于构造代码块执行,构造代码块先于构造方法执行。
测试:
public class Person {
private String field = "我是person中的一般属性";
private static String staticField = "我是person中的静态属性";
public Person(){
super();
System.out.println("我是person中的默认无参数构造方法");
}
public void method() {
System.out.println("我是person中的一般方法");
}
public static void staticMethod(){
System.out.println("我是person中的静态方法");
}
{
System.out.println(Person.staticField);
Person.staticMethod();
System.out.println(this.field);
this.method();
System.out.println("我是person中的构造代码块");
}
static{
System.out.println(Person.staticField);
Person.staticMethod();
System.out.println("我是person中的静态代码块");
}
}
public class TestMain {
public static void main(String[] args) {
Person p = new Person();
}
}
测试结果:
我是person中的静态属性
我是person中的静态方法
我是person中的静态代码块
我是person中的静态属性
我是person中的静态方法
我是person中的一般属性
我是person中的一般方法
我是person中的构造代码块
我是person中的默认无参数构造方法
打印结果解读:
当我们通过new的方式创建Person对象时,会调用Person类的构造方法,通过上面的分析我们可以知道,静态代码块先于构造代码块执行,构造代码块先于构造方法执行。而类成员的加载顺序:一般属性、一般方法、构造代码块、构造方法、静态属性、静态方法、静态代码块,所以静态代码块执行的时候可以访问到静态元素和静态方法,而构造代码块执行的时候可以访问到静态属性、静态方法、一般属性、一般方法,最后是构造方法的执行。
注意:静态元素不能访问非静态元素(一对多,不明确访问哪个对象的属性或者方法),而非静态元素可以访问静态元素(多对一,静态元素有且只有一份,明确访问)。
综上可得到类对象的创建过程(未考虑父类的情况):
- 加载类的一般属性、一般方法、构造代码块、构造方法。
- 开辟一块静态元素空间,加载类的静态属性、静态方法、静态代码块。
- 执行类的静态代码块。
- 在堆内存中开辟一块空间,用于表示该类的对象。
- 在该对象空间内加载类的一般属性,this属性,持有类的信息。
- 执行对象的构造代码块。
- 执行对象的构造方法。
- 复制一份对象空间中的this属性值给栈内存中的变量。
存在继承关系的类成员加载执行过程
上文我们忽略当前类的父类的加载过程,接下来我们来探讨一下父类的加载过程对当前类的加载过程的影响。
引入了父类的加载过程,无非就是在在加载子类属性或者方法之前先加载父类的属性和方法,这好比先有父亲再有儿子的关系,其实编程的思维和我们现实生活中的考虑问题的思维很像。
存在继承关系的子类对象创建过程:
- 加载父类的一般属性、一般方法、构造代码块、构造方法。
- 开辟一块属于父类的静态元素空间,加载静态属性、静态方法、静态代码块。
- 执行父类的静态代码块。
- 加载子类的一般属性、一般方法、构造代码块、构造方法。
- 开辟一块属于子类的静态元素空间,加载静态属性、静态方法、静态代码块。
- 执行子类的静态代码块。
- 在堆内存中开辟一块子类对象空间。
- 在子类对象空间中开辟一块名为super的父类对象空间,并在这块空间中加载父类的一般属性、持有类的信息。
- 执行父类的构造代码块。
- 执行父类的构造方法。
- 在子类对象空间中加载子类的一般属性、this属性、持有类的信息。
- 执行子类的构造代码块。
- 执行子类的构造方法。
- 将子类对象空间中的this属性值复制一份给栈内存中的子类类型变量。
过程图如下:
测试:
public class Person {
private String field = "我是person中的一般属性";
private static String staticField = "我是person中的静态属性";
public Person(){
super();
System.out.println("我是person中的默认无参数构造方法");
}
public void studentMethod() {
System.out.println("我是person中的一般方法");
}
public static void staticMethod(){
System.out.println("我是person中的静态方法");
}
{
System.out.println(Person.staticField);
Person.staticMethod();
System.out.println(this.field);
this.studentMethod();
System.out.println("我是person中的构造代码块");
}
static{
System.out.println(Person.staticField);
Person.staticMethod();
System.out.println("我是person中的静态代码块");
}
}
public class Student extends Person {
private String field = "我是student中的一般属性";
private static String staticField = "我是student中的静态属性";
public Student() {
super();
System.out.println("我是student中的默认无参数构造方法");
}
public void method() {
System.out.println("我是student中的一般方法");
}
public static void staticMethod() {
System.out.println("我是student中的静态方法");
}
{
System.out.println(Student.staticField);
Student.staticMethod();
System.out.println(this.field);
this.method();
System.out.println("我是student中的构造代码块");
}
static{
System.out.println(Student.staticField);
Student.staticMethod();
System.out.println("我是student中的静态代码块");
}
}
public class TestMain {
public static void main(String[] args) {
Student s = new Student();
}
}
测试结果:
我是person中的静态属性
我是person中的静态方法
我是person中的静态代码块
我是student中的静态属性
我是student中的静态方法
我是student中的静态代码块
我是person中的静态属性
我是person中的静态方法
我是person中的一般属性
我是person中的一般方法
我是person中的构造代码块
我是person中的默认无参数构造方法
我是student中的静态属性
我是student中的静态方法
我是student中的一般属性
我是student中的一般方法
我是student中的构造代码块
我是student中的默认无参数构造方法