1 类加载总过程
类加载子系统负责从文件系统或者网络加载Class文件,Class文件在文件开头有特定的文件标识
image-20210111225448746
- class file存在本地硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载到JVM当中来,根据这个文件实例化n个一模一样的实例
- class file加载到JVM中,被称为DNA元数据模板,放在方法区
- 在.class文件-> JVM-> 最终成为元数据模板,此过程就要一个运输工具(Class Loader)
ClassLoader 只负责Class文件的加载,至于它是否能运行,则由Execution Engin 决定
加载的类信息存放于一块成为方法区的内存空间。除了类的信息外(大的Class对象实例),方法区还会存放运行时常量池信息,可能还包括字符串字面量和数字敞亮(这部分敞亮信息是Class文件中常量池部分的内存映射)
我们可以用javap -v ***.class来反编译字节码文件,然后查看里面的Class结构
image-20210111225512451这里我们结构里面有一个常量池
1.1 ClassLoader加载class文件的过程
用图来演示就是这样
- 我们需要运行HelloLoader的main方法,首先需要编译出来一个class文件
- class文件由ClassLoader加载到内存当中
- 判断是否已经装载过了,没装载的就进行装载,装载的就进行链接,初始化和调用main方法
- 如果装载有问题就会报错,比如我们常见的因为版本冲突的问题会报错找不到类
1.2 加载过程
- 通过一个类的全限定名获取定义此类的二进制字节流
- 本地文件系统
- 通过网络获取,web Applet
- 从zip压缩包中读取,成为日后jar,war格式的基础
- 运行时计算生成,使用最多的是:动态代理技术
- 由其他文件生成,典型场景:JSP应用
- 从专有数据库中提取.class文件,比较少见
- 从加密文件中获取,典型的防Class文件被反编译的保护措施
- 将这个字节流所代表的静态存储结构转化为方法区运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
1.3 链接过程
1.3.1 验证(vefiry)
目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全
-
主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证
比如说字节码文件要以CAFEBABE开头
image-20210111225602521
1.3.2 准备(prepare)
为类变量分配内存并且设置该类变量的默认初始值,即零值
-
这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显示初始化赋值
如果被final修饰的话,就不是一个变量了,会变成一个常量,这时在编译的时候就会被分配了,只会在prepare的阶段显式初始化和赋值
这里不会为实例变量分配初始化,类变量会分配在方法去,而实例变量实惠随着对象一起分配到java堆中
比如:
public class HelloApp { private static int a = 1;//prepare:a = 0 ---> initial : a = 1 public static void main(String[] args) { System.out.println(a); } }
a的值在prepare的时候会被初始化为0,而在initialization的时候才会被赋值为1
1.3.3 解析(resolve)
-
将常量池内的符号引用转换为直接引用的过程
image-20210111225614330比如说这些的标号就是一个符号引用
事实上,解析操作旺旺会伴随着JVM在执行完初始化之后再执行
符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《java虚拟机规范》的Class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个简洁定位到目标的句柄。
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT_Class_info,CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。
1.4 初始化
初始化阶段就是执行类构造器方法<clinit>()的过程
此方法不需定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来
image-20210111230645760比如该ClassinitTest的类,我们并没有声明一个clinit的方法,而是编译器自己根据类变量赋值和静态代码块来生成的。
public class ClassInitTest { private static int num = 1; static{ num = 2; number = 20; System.out.println(num); //System.out.println(number);//报错:非法的前向引用。 } private static int number = 10; //linking之prepare: number = 0 --> initial: 20 --> 10 public static void main(String[] args) { System.out.println(ClassInitTest.num);//2 System.out.println(ClassInitTest.number);//10 } }
另外需要注意的是:
- 如果在上述代码里面,number的赋值是在prepare里面就已经初始化为0,然后在initialization里面的<clinit>方法会被根据代码的顺序先被赋值为20,再赋值为10(可以从字节码的角度看得很清晰)。
- 静态声明在后面,就不能在前面的静态代码块调用这个变量,会报错:非法的前向引用。
构造器方法中指令按语句在源文件中出现的顺序执行
<clinit>()不同于类的构造器。(关联:构造器是就是字节码里面的<init>())
如果没有静态代码块和类变量的赋值,那么就不会有<clinit>()
若该类具有父类,JVM会保证自身的<clinit>()执行前,父类的<clinit>()已经执行完毕
虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁
比如说:
public class DeadThreadTest { public static void main(String[] args) { Runnable r = () -> { System.out.println(Thread.currentThread().getName() + "开始"); DeadThread dead = new DeadThread(); System.out.println(Thread.currentThread().getName() + "结束"); }; Thread t1 = new Thread(r, "线程1"); Thread t2 = new Thread(r, "线程2"); t1.start(); t2.start(); } } class DeadThread { static { if (true) { System.out.println(Thread.currentThread().getName() + "初始化当前类"); while (true) { } } } }
输出的结果是:
image-20210122213613609发现这里只会加载一次,线程1加载了一次静态代码块,线程2不会被执行
2 类加载器
JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。
从概念上来讲,自定义类加载器一般只的是程序中由开发人员自定义的一类加载器,但是Java虚拟机规范却没有这么定义,而是讲所有派生于抽象类ClassLoader的类加载器都划分为自定义加载器
-
无论类加载器的类型如何划分,在程序中我们最场景的类加载器始终只有3个,如下图:
image-20210122214905443- 引导类加载器
- 扩展类加载器
- 系统类加载器
类图如下:
image-20210122215953883
从代码层面看的话,如下:
在Launcher的类里面有两个内部类AppClassLoader和ExtClassLoader,这两个都都继承了URLClassLoader。
2.1 三种加载器例子
public class ClassLoaderTest {
public static void main(String[] args) {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//获取其上层:扩展类加载器 ?
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d
//获取其上层:获取不到引导类加载器
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println(bootstrapClassLoader);//null
//对于用户自定义类来说:默认使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载的。
ClassLoader classLoader1 = String.class.getClassLoader();
System.out.println(classLoader1);//null
}
}
疑问:
-
明明AppClassLoader和ExtClassLoader是同级的,为啥getParent会得到到ExtClassLoader?
这个的parent并不是真正的父类,只是一种组合关系,或者说ExtClassLoader是由AppClassLoader进行加载的。
2.2 Bootstrap ClassLoader
2.3 Extension ClassLoader
2.4 AppClassLoader
2.5 三种加载器演示
public class ClassLoaderTest1 {
public static void main(String[] args) {
System.out.println("**********启动类加载器**************");
//获取BootstrapClassLoader能够加载的api的路径
URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL element : urLs) {
System.out.println(element.toExternalForm());
}
//从上面的路径中随意选择一个类,来看看他的类加载器是什么:引导类加载器
ClassLoader classLoader = Provider.class.getClassLoader();
System.out.println(classLoader);
System.out.println("***********扩展类加载器*************");
String extDirs = System.getProperty("java.ext.dirs");
for (String path : extDirs.split(";")) {
System.out.println(path);
}
//从上面的路径中随意选择一个类,来看看他的类加载器是什么:扩展类加载器
ClassLoader classLoader1 = CurveDB.class.getClassLoader();
System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@1540e19d
}
}
输出以下:
**********启动类加载器**************
file:/D:/leanningPrograms/jdk1_8/jre/lib/resources.jar
file:/D:/leanningPrograms/jdk1_8/jre/lib/rt.jar
file:/D:/leanningPrograms/jdk1_8/jre/lib/sunrsasign.jar
file:/D:/leanningPrograms/jdk1_8/jre/lib/jsse.jar
file:/D:/leanningPrograms/jdk1_8/jre/lib/jce.jar
file:/D:/leanningPrograms/jdk1_8/jre/lib/charsets.jar
file:/D:/leanningPrograms/jdk1_8/jre/lib/jfr.jar
file:/D:/leanningPrograms/jdk1_8/jre/classes
null
***********扩展类加载器*************
D:\leanningPrograms\jdk1_8\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext
sun.misc.Launcher$ExtClassLoader@7ea987ac