前言 |
学习了很久的面向对象的知识,我们对于“类”这个概念一定不陌生吧,当我们需要一个对象的时候,都是通过“类”得到,包括我们学习的反射,也是通过拿到类中的一些信息,今天就来总结一下,JVM是如果把类加载到内存当中的
思维导图 |
什么是类加载? |
JVM把class文件加载到内存里面,并对数据进行校验、准备、解析和初始化,最终能够被形成被JVM可以直接使用的Java类型的过程。
类加载的过程 |
类加载过程经历一下几个过程,首先是加载,然后是连接(连接分为三个小步骤:验证,准备,解析),初始化,之后就可以使用了,但是他们的顺序也并不是完全按照这一个过程,也有交叉的部分,比如说加载和验证就有可能出现交叉的情况,验证的符号引用验证就是在解析阶段发生的,下边我们来总结一下这几个步骤都分别完成了怎样的工作
一、加载(3件事)
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。(反射对象)
二、验证(确保加载的类信息符合JVM规范,没有安全问题)
- 文件格式的验证
- 元数据验证
- 字节码验证
- 符号引用验证
三、准备
正式为类变量(static)分配内存,并设置类变量初始值,这些内存都将在方法区中进行分配
public static int value=123; //初始后为 value=0;
//对于static final类型,在准备阶段会被赋予正确的值
public static final value=123;//初始化为 value=123;
//如果是boolean值默认赋值为:false
//如果是对象引用默认赋值为:null
四、解析
将常量池内的符号引用替换为直接引用的过程
1. 符号引用:简单的理解就是字符串,比如引用一个类,java.util.ArrayList 这就是一个符号引用,字符串引用的对象不一定被加载。
2.直接引用:指针或者地址偏移量。引用对象一定在内存(已经加载)。
五、初始化
- 执行类构造器<clinit>()的方法
- 这个方法由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的
- 子类的调用前保证父类的<clinit>被调用
- <clinit>()方法在多线程环境中被正确加锁和同步
注意:
<clinit>()是线程安全的,执行 <clinit>()的线程需要先获取锁才能进行初始化操作,保证只有一个线程能执行 <clinit>()(利用此特性可以实现线程安全的懒汉单例模式)。
Demo——在内存中的变化 |
内存分析图:
1.右边绿色部分是加载Demo1和A类的过程
2.将main方法的栈帧压入栈中,然后在堆中生成a对象,将引用地址赋给a
3.将A的构造方法栈帧压入栈中,打印类A方法区的静态资源
什么时候会进行类的加载? |
主动引用(一定会发生类的初始化)
- new一个对象。
- 调用类的静态成员(除了final常量)和静态方法。
- 通过反射对类进行调用。
- 虚拟机启动,main方法所在类被提前初始化。
- 初始化一个类,如果其父类没有初始化,则先初始化父类。
被动引用(不会发生类的初始化)
- 当访问一个静态变量时,只有真正声明这个变量的类才会初始化。(子类调用父类的静态变量,只有父类初始化,子类不初始化)。
- 通过数组定义类引用,不会触发此类的初始化。
- final变量不会触发此类的初始化,因为在编译阶段就存储在常量池中。
public class Demo1 {
static{
System.out.println("静态初始化Demo1");
}
public static void main(String[] args) {
//1. age真正的类会被初始化
System.out.println(B.age);
//2. 数组不会初始化
A[] as=new A[10];
//3.调用类的final常量不会初始化A:被动引用
System.out.println(A.MAX);
}
}
class A extends A_Father {
public static int age = 24;
public static final int MAX=100;
static {
System.out.println("静态初始化类A");
age = 25;
}
public A() {
System.out.println("创建A类的对象");
}
}
class A_Father{
static{
System.out.println("静态初始化A_Father");
}
}
class B extends A{
static{
System.out.println("静态初始化B");
}
}