1. 类加载
编译,即把我们写好的java文件,通过javac命令编译成字节码,也就是我们常说的.class文件。 运行,则是把编译声称的.class文件交给Java虚拟机(JVM)执行。
类加载的过程是指JVM(虚拟机)将.class文件中的信息加载进内存,并解析生成对应的class对象的过程。
举个通俗点的例子来说,JVM在执行某段代码时,遇到了class A, 然而此时内存中并没有class A的相关信息,于是JVM就会到相应的class文件中去寻找class A的类信息,并加载进内存中,这就是我们所说的类加载过程。
由此可见, 不是一开始就把所有的类都加载进内存中,而是只有第一次遇到某个需要运行的类时才会加载,且只加载一次。
类加载
类加载的过程主要分为三个部分:
- 加载: 加载指的是把class字节码文件查找并装载入内存中
- 链接
- 初始化:这个阶段主要是对类变量初始化,是执行类构造器<clinit>()方法的过程(到了初始化阶段,用户定义的 Java 程序代码才真正开始执行。)
而链接又可以细分为三个小部分:
- 验证:主要是为了确保类加载的正确性
- 准备:主要是为类变量(注意,不是实例变量)分配内存,并且赋予初值
- 解析:将常量池内的符号引用替换为直接引用的过程。
类加载器
类加载器负责加载所有的类,其为所有被载入内存中的类生成一个java.lang.Class实例对象。一旦一个类被加载入JVM中,同一个类就不会被再次载入了。正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识。
JAVA中的类加载器主要分为下图的四种:
![0a400ffe9938f4e0936a1ec14b218aed.png](https://i-blog.csdnimg.cn/blog_migrate/be72c2f95e3171256a8fd9b10636e8de.jpeg)
引导类加载器(bootstrap class loader)
(1)它用来加载 Java 的核心库(JAVA_HOME/jre/lib/rt.jar,sun.boot.class.path路径下的内容),是用原生代码(C语言)来实现的,并不继承自 java.lang.ClassLoader。
(2)加载扩展类和应用程序类加载器。并指定他们的父类加载器。
扩展类加载器(extensions class loader)
(1)用来加载 Java 的扩展库(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路径下的内容) 。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java类。 (2)由sun.misc.Launcher$ExtClassLoader实现。
应用程序类加载器(application class loader)
(1)它根据 Java 应用的类路径(classpath,java.class.path 路径下的内容)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。
(2)由sun.misc.Launcher$AppClassLoader实现
自定义类加载器
(1)开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。
(2)遵守双亲委派模型:继承ClassLoader,重写findClass()方法。
(3)破坏双亲委派模型:继承ClassLoader,重写loadClass()方法。
双亲委派模型 双亲委派的执行流程:
如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。
双亲委派模型的好处
(1)主要是为了安全性,避免用户自己编写的类动态替换 Java的一些核心类,比如 String。
(2)同时也避免了类的重复加载,因为 JVM中区分不同类,不仅仅是根据类名,相同的 class文件被不同的 ClassLoader加载就是不同的两个类。