动态和静态加载以及流程

动态和静态加载

静态加载

编译时加载相关的类,如果没有则报错,依赖性太强。

下面的代码,我们不会使用到Dog这个类,但是编译会给我们直接报错,依赖性太强,要求所有的类必须被加载好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.zss.reflact;

/**
* @author zss
*/

public class Test {
public static void main(String[] args) {
int a=0;
if (a==1){
Dog dog=new Dog();
dog.cry();}
else {
System.out.println("你对的");
}
}
}

image-20220321193758779

动态加载

运行时需要加载的类,如果运行时不要该类,则不报错,降低了依赖性.下面的这一段代码,路径根本不存在,但是在运行过程中不会使用到该代码,所以不会本加载,当使用时才会被加载。反射属于动态加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.zss.reflact;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* @author zss
*/

public class Test {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
int a=0;
if (a==1){
Class cls=Class.forName("java//不存在");
Object O=cls.newInstance();
Method method=cls.getMethod("HI");
method.invoke(O);
}
else {
System.out.println("你对的");
}
}
}

image-20220321194532855

类加载的时机

  1. 创建对象时new
  2. 子类被加载时,父类也被加载
  3. 调用类中静态成员时
  4. 通过反射时

类加载的过程

image-20220321200304845

在加载中,将类的class文件读入内存,并且为之创建一个java.lang.Class对象,此过程由类加载器完成。验证与文件安全性(字节码等)进行验证,准备阶段分配空间,解析阶段把符号引用转成字节引用。初始化过程中可以由程序员进行控制,JVM负责对类进行初始化,这里主要指静态成员

image-20220321200557645

类加载的五个阶段

加载阶段

JVM在该阶段主要目的是将字节码从不同的数据源(.class文件,jar包,网络文件等)转换为二进制字节流加载到内存中,并且生成一个代表该类的java.lang.Class对象

验证阶段

  1. 保证Class为文件的字节流中包含信息符合虚拟机的要求,并且不会危害虚拟机自身安全
  2. 文件格式验证(是否以魔数oxcafebabe开头)、元数据验证,字节码验证和符号引用验证
  3. 可以考虑使用-Xverify:none 参数关闭大部分验证措施,缩短虚拟机加载的时间

准备阶段

JVM对在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始化值比如int为0,String为null等),这些变量所使用的内存都将在方法区中进行分配。在这里只会对静态变量分配内存,实例变量不会分配,而对于final的常量会一次性分配

解析阶段

虚拟机将常量池的符号引用变成直接引用,直接引用利用地址进行相互引用

初始化阶段

  1. 初始化阶段才是真正执行类中定义的java程序代码,此阶段是执行()方法的过程

  2. () 方法是由编译器按语句在源文件出现的顺序,依次自动收集类中所有静态变量的赋值动作和静态代码块中的语句并进行合并,比如会将其进行合并,而最终num=200

    1
    2
    int static num=100;
    num=200;
  3. 虚拟机会保证一个类()方法在多线程中被正确的枷锁,同步,如果多个线程同时初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕