我在B站的账号Seven的代码实验室,里面有对应的系列视频教程,希望大家多多支持。
接上节我们了解了class文件是如果被加载进JVM的,本节讲讲class被加载进JVM后是如何执行java代码的。
Java执行代码的大致流程
JVM执行Java代码大致有8个步骤,下面用一张图全部描述清楚
- 1.启动虚拟机(C++创建)
- 2.创建一个引导类加载器实例(BootstrapClassLoader)
- 3.C++调用Java代码创建JVM启动器,创建sun.misc.Launcher实例(该类由引导类加载器加载创建其他的类加载器)
- 4.sun.misc.Launcher.getLauncher() 获取运行类自己的加载器ClassLoader --> AppClassLoader
- 5.获取到ClassLoader后调用loadClass(“A”)方法加载运行的类A
- 6.加载完成执行A类的main方法
- 7.程序运行结束
- 8.JVM销毁
类加载的步骤
loadClass是类加载中最核心的方法,在后面讲双亲委派的时候会细讲,现在先讲一下类加载中最核心的五个步骤:
- 1.加载:通过IO流读取类的字节码文件
- 2.验证:校验加载进来的字节码文件是否符合JVM规范
- 3.准备:给静态变量分配内存并赋予默认值
- 4.解析:将符号引用替换为直接引用
- 5.初始化:将静态变量初始化为指定的值,同时执行静态代码块的代码
类加载器和双亲委派机制
类加载器
首先看看类加载器有哪几种?
- 1.BootstrapClassLoader(引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等)
这个是最顶层的类加载器 - 2.ExtClassLoader(扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包)
- 3.AppClassLoader(应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载我们应用中自己写的那些类)
- 4.自定义加载器:负责加载用户自定义路径下的类包
这个是我们自己定义的类加载器,可以根据实际的需求定制自己类加载器去加载对应的class文件。比如可以定制一个实现热部署的类加载器。
Launcher类的构造方法分析
Launcher的构造方法如下:
可以看到Launcher构造方法内部创建了两个类加载器,分别是sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应用类加载器),JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序。
双亲委派机制
接下来我们讲双亲委派机制,先上定义,用大白话给大家讲一下什么叫双亲委派。
定义:当我们需要加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类(注意这里说的父类并不是传统意义上说的继承的父类,他们之间没有继承关系,所以应该说上层类加载器更准确一些)
loadClass源码如下:
通过源码可以看到双亲委派的执行步骤:
- 1.首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回
- 2.如果此类没有加载过,那么再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载
- 3.如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法 【调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类】来完成类加载
双亲委派的优点
使用双亲委派的方式来加载类,主要有两个方面的好处:
- 沙箱安全机制:比如我们自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
- 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性
全盘负责委托机制
最后讲一下全盘负责委托机制,这个比较好理解。
“全盘负责”是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类所依赖及引用的类也由这个ClassLoder载入。
比如我们的类 A中引用了 类B,由于全盘负责委托机制 ,类B也将有加载类A的加载器来加载,除非你显示的使用另外一个ClassLoder。