类加载基本过程
- 加载:
1、通过一个类的全限定名获取描述此类的二进制字节流;
2、将这个字节流所代表的静态存储结构保存为方法区的运行时数据结构;
3、在java堆中生成一个代表这个类的java.lang.Class对象,作为访问方法区的入口;
- 链接
如果该类依赖一些其他类,链接过程就是把依赖内容进行引入;
- 初始化
初始化静态成员,并且执行静态代码块。
关于类加载中的常见问题
1、什么时机会触发某个类的加载
a)构造该类的实例
b)调用该类的静态属性或者方法
c)使用子类时会触发父类的加载
典型代码深入理解加载过程
/**
* @ Created with IntelliJ IDEA.
* @ClassName Test1
* @Description
* @Author by小房
* @Date 2020/8/2 16:55
*/
class AA {
public AA() {
System.out.println("AA.构造");
}
{
System.out.println("AA.{}");
}
static {
System.out.println("AA.static {}");
}
}
class BB extends AA {
public BB() {
System.out.println("BB.构造");
}
{
System.out.println("BB.{}");
}
static {
System.out.println("BB.static {}");
}
}
public class Test1 extends BB{
public static void main(String[] args) {
System.out.println("开始");
new BB();
new BB();
System.out.println("结束");
}
}
加载过程分析
1、代码先从main()方法执行,main是Test1 这个类的静态方法,就会触发 Test1 的类加载。
2、BB 是Test1 的父类,在加载 Test1 之前会先加载 BB
3、AA 又是 BB 的父类,加载BB 之前又会先加载 AA
过程:加载AA -> 加载BB -> 加载Test1 -> 执行main 方法
4、加载AA 调用其中的静态代码块
static {
System.out.println("AA.static {}");
}
5、加载 BB 调用BB 的静态代码块
static {
System.out.println("BB.static {}");
}
6、Test1 没有静态代码块,执行main 方法
System.out.println("开始");
7.第一次执行
new BB();
构造 BB 的实例会先构造 AA 的实例,先执行 AA 中的代码块和构造方法,在执行 BB的。
{
System.out.println("AA.{}");
}
public AA() {
System.out.println("AA.构造");
}
{
System.out.println("BB.{}");
}
public AA() {
System.out.println("BB.构造");
}
8、第二次执行
new BB();
构造 BB 的实例会先构造 AA 的实例,先执行 AA 中的代码块和构造方法,在执行 BB的。
{
System.out.println("AA.{}");
}
public AA() {
System.out.println("AA.构造");
}
{
System.out.println("BB.{}");
}
public AA() {
System.out.println("BB.构造");
}
9、执行
System.out.println("结束");
注意:类加载只加载一次,也就是static 代码块只执行一次
最终输出结果为:
AA.static {}
BB.static {}
开始
AA.{}
AA.构造
BB.{}
BB.构造
AA.{}
AA.构造
BB.{}
BB.构造
结束
如果 Test1 不继承自 BB的话,那么Test1 的加载就不会触发 AA 和 BB 的加载,就会是下边的执行结果:
开始
AA.static {}
BB.static {}
AA.{}
AA.构造
BB.{}
BB.构造
AA.{}
AA.构造
BB.{}
BB.构造
结束
2、类加载器有哪些
虚拟机设计团队把加载动作放到JVM外部实现,以便让应用程序决定如何获取所需的类,实现这个动作的代码称为“类加载器”,JVM提供了3种类加载器:
1、启动类加载器(Bootstrap ClassLoader):负责加载 JAVAHOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。 比如;String …
2、扩展类加载器(Extension ClassLoader):负责加载 JAVAHOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
3、应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。
我们可以通过下边代码看一下:
import sun.net.spi.nameservice.dns.DNSNameService;
public class Demo3 {
public static void main(String[] args) {
ClassLoader classLoader1 = Demo3.class.getClassLoader();
System.out.println(classLoader1);
ClassLoader classLoader2 = String.class.getClassLoader();
System.out.println(classLoader2);
ClassLoader classLoader3 = DNSNameService.class.getClassLoader();
System.out.println(classLoader3);
}
}
输出结果为:
sun.misc.Launcher$AppClassLoader@18b4aac2
null
sun.misc.Launcher$ExtClassLoader@677327b6
JVM基于上述类加载器,通过双亲委派模型进行类的加载,当然我们也可以通过继承java.lang.ClassLoader实现自定义的类加载器。
3、什么是双亲委派模型
双亲委派模型工作过程:当一个类加载器收到类加载任务,优先交给其父类加载器去完成,因此最终加载任务都会传递到顶层的启动类加载器,只有当父类加载器无法完成加载任务时,才会尝试执行加载任务。
目的:就是优先加载标准库的类
流程
1、先从 AppClassLoader 开始
2、AppClassLoader 不会立刻查找,而是先把类名交给它的父亲,先让父亲查找;
3、ExtClassLoader 也不会立刻进行查找,也是交给它的父亲
4、BootstrapClassLoader 拿到类名之后,只能自己亲自 在rt.jar查找,如果找到了就可以直接加载类就好了,但是如果没有找到,就在把类名交还给 ExtClassLoader 查找
5、ExtClassLoader 再根据类名在ext目录中查找,如果找到了就加载,如果没有找到就把类名交还给 AppClassLoader 进行加载;
6、AppClassLoader 再去 CLASS_PATH 环境变量, -cp 指定的目录, 以及当前目录进行查找,若果找到了就加载,没有找到就抛出 ClassNotFoundException。
4、能否违背双亲委派模型
答案是可以的,只是标准库中的三个类加载器要遵守,其他类加载器不太需要遵守,比如 Tomcat 中的类加载器就没有遵守。而是从webapps 目录中加载指定的 webapp 的类。