目录
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称为虚拟机的类加载机制。
Java语言里类型的加载、连接、初始化都是在程序运行期间完成的。例如,编写一个面向接口的应用程序,可以等到运行时再指定其实际的实现类,用户可以通过Java预置的或者自定义类加载器,将某个应用程序在运行时从网络或者本机磁盘加载一个二进制流文件作为程序的一部分(比如SPI机制)。这都依赖Java语言运行期类才加载诞生的。
一个类被加载的过程包括加载、链接(验证、准备、解析)、初始化。


注意:这里类之间的关系不是继承关系而是它们之间存在这样的加载顺序。
1.类加载器先查询缓存,判断是否加载过 findLoaderClass,若加载过直接返回
2.若没有加载过,向上委托:自底向上检查该类是否已经加载
3.直到BootStrap ClassLoader,findClass是不是归自己管理。是:加载
4.否:向下委派:自顶向下进行实际查找和加载
5.类似递归的过程

-
-
- /**
- * 类加载器
- *
- * @author Promsing(张有博)
- * @version 1.0.0
- * @since 2022/8/6 - 11:23
- */
- public class ClassLoadLevel {
-
- public static void main(String[] args) {
- System.out.println(String.class.getClassLoader());
- //期望是bootstrap,但是显示null,null就是bootstrap
- //bootstrap是C++实现的,Java中没有与它做对应,所以空值
-
- System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());
- //期望是extension,但是显示extension
-
- System.out.println(JSONObject.class.getClassLoader()); //application
- //期望是application,但是显示AppClassLoader
-
- System.out.println(ClassLoadLevel.class.getClassLoader());
- //期望是application,但是显示AppClassLoader
-
- System.out.println("----加载器的加载器,加载器的加载器不是父加载器---------------");
-
-
- System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getClass().getClassLoader());
- //期望是bootstrap,但是显示null,null就是bootstrap
-
- System.out.println(JSONObject.class.getClassLoader().getClass().getClassLoader()); //application
- //期望是bootstrap,但是显示null,null就是bootstrap
-
- System.out.println(ClassLoadLevel.class.getClassLoader().getClass().getClassLoader());
- //期望是bootstrap,但是显示null,null就是bootstrap
-
-
- System.out.println("----加载器的父加载器---------------------");
-
- System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getParent());
- //期望是bootstrap,但是显示null,null就是bootstrap
-
- System.out.println(JSONObject.class.getClassLoader().getParent()); //application
- //期望是extension,但是显示extension
-
- System.out.println(ClassLoadLevel.class.getClassLoader().getParent());
- //期望是extension,但是显示extension
- }
-
- }
- /**
- * 自定义类加载器
- *
- * @author Promsing(张有博)
- * @version 1.0.0
- * @since 2022/8/6 - 15:23
- */
- public class CustomClassLoad extends ClassLoader {
-
- @Override
- protected Class> findClass(String name) throws ClassNotFoundException {
-
- try {
- String concat = name.replaceAll(".", "/");
- String replace = name.replace(".", "/");
- //file转字节数组
- File f=new File("F:\\09 个人学习\\study-designn-pattern\\src\\main\\java\\",name.replace(".","/").concat(".class"));
- FileInputStream fis=new FileInputStream(f);
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
-
- int b=0;//b是实际的内容
-
- while ((b=fis.read())!=0){
- baos.write(b);
- }
-
- byte[] bytes = baos.toByteArray();
- baos.close();
- fis.close();
-
- //把字节码转化成java.lang.Class
- return defineClass(name,bytes,0,bytes.length);
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- return super.findClass(name);
- }
-
- public static void main(String[] args) throws Exception {
- CustomClassLoad load=new CustomClassLoad();
- Class> aClass = load.loadClass("com.promsing.creational.singleton.Person");
- System.out.println(aClass.getName());
-
- Object o = aClass.newInstance();
- System.out.println(o.toString());
- }
- }
AppClassLoader--》URLClassLoader--》SecureClassLoader--》ClassLoader
ExtClassLoader--》URLClassLoader--》SecureClassLoader--》ClassLoader

加载的方法:ClassLoader.loadClass(name,resolve)方法。跟上面刚说的一致: 一个类被加载都内存所经历的过程
1.先findLoaderClass 看是否被加载过
2.变量parent被final修饰了,无法更改加载顺序。
3.//注意注意:这里就是向上委托的过程
c = parent.loadClass(name, false);
4. //注意注意:这里就是向下委派的过程,
代码上不直观,好好想一想递归
5.这个类似一个递归的过程,当父加载器找不到时,就回到了子加载器。这就是委派的过程
6.具体过程看代码注释
loadClass() 主要进行类加载的方法,默认的双亲委派机制就实现在这个方法中
findClass() 根据名称或位置加载.class字节码
definClass() 把字节码转化成java.lang.Class
- protected Class> loadClass(String name, boolean resolve)
- throws ClassNotFoundException
- {
- synchronized (getClassLoadingLock(name)) { //加同步锁
- // First, check if the class has already been loaded
- Class> c = findLoadedClass(name);//是否被加载过
- if (c == null) {
- long t0 = System.nanoTime();
- try {
- //AppClassLoader的父类是ExtClassLoader,依次往上
- if (parent != null) {
- //父类的loadClass方法也是调用的ClassLoader.loadClass
- //注意注意:这里就是向上委托的过程
- c = parent.loadClass(name, false);
- } else {
- //直到BootStrapClassLoader没有父类了,调用c++代码加载class文件
- c = findBootstrapClassOrNull(name);
- }
- } catch (ClassNotFoundException e) {
- // ClassNotFoundException thrown if class not found
- // from the non-null parent class loader
- }
- //注意注意:这里就是向下委派的过程,代码上不直观,好好想一想递归
-
- //首先是ExtClassLoader中的c == null然后执行,返回
- //接着是AppClassLoader中的c == null
- if (c == null) {
- // If still not found, then invoke findClass in order
- // to find the class.
- long t1 = System.nanoTime();
- //最终AppClassLoader中执行findClass找到了我们自定义的类
- //看下文
- c = findClass(name);
-
- // this is the defining class loader; record the stats
- sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
- sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
- sun.misc.PerfCounter.getFindClasses().increment();
- }
- }
- if (resolve) {
- resolveClass(c);
- }
- return c;
- }
-

双亲委派的指的是,类加载由最底下的自定义加载器开始,将加载请求委派给上级,如果上级已经加载过该类,则直接加载完成,如果没有则继续递归至顶级Bootstrap加载器,然后父类判断如果该类不属于自己的加载范畴则委派子类加载。继续往下递归。
如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委派父类加载器去完成。每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个请求(他的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
1.保证了JVM提供的核心类不被篡改,保证class执行安全
2.保证一个类只能被加载一次、防止重复加载同一个class,每个类加载器只会加载自己负责的部分,这样每个类只会被加载一次。

原因:
1. 在JDBC 4.0之后实际上我们不需要再调用Class.forName来加载驱动程序了,我们只需要把驱动的jar包放到工程的类加载路径里,那么驱动就会被自动加载。 这个自动加载采用的技术叫作SPI,数据库驱动厂商也都做了更新。可以看一下jar包里面的META-INF/services目录,里面有一个java.sql.Driver的文件,文件里面包含了驱动的全路径名。
但是,类加载器受到加载范围的限制,在某些情况下父类加载器无法加载到需要的文件,需要委托子类加载器去加载class文件。
解决办法:
1. DriverManager封装了静态代码块来实现这一加载过程。
2. 静态代码块中调用 loadInitialDrivers() 方法,并调用ServiceLoader.load(Driver.class) 加载所有在META-INF/services/java.sql.Driver 文件里边的类到JVM内存,完成驱动的自动加载。
原因
1.一个容器部署两个程序
一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每一个应用程序的类库都是独立的,保证相互隔离
2.部署在同一个容器的程序要共享类库
部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机
3.web容器的类库与程序类库隔离
web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。
4.web容器需要修改jsp后不用重启
web容器要支持jsp修改,我们知道,jso文件最终也要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情,web容器需要支持jsp修改后不用重启
解决
1.CommonClassLoader
commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;CommonClassLoader能加载的类都可以被Catalina ClassLoader和SharedClassLoader使用,从而实现了公有类库的共用
2.CatalinaClassLoader
catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
3.SharedClassLoader
sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见
4.WebappClassLoader
WebappLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见
5.JsperClassLoader
以上为5之前的tomcat,5之后的tomcat只有common 和 webapp 以及 jsp ,其余两个需要自己定制
原因:
1.需要被热部署
2.利用同一个class文件不同的类加载器在内存创建出两个不同的class对象
解决:
1.自定义类加载器:重写方法
2.通过一个线程去监听文件的修改时间
3.重写findClass方法
4.把文件以流的形式读进来,然后调defineClass方法
验证文件是否符合JVM规范
静态成员变量赋默认值
将类、方法、属性等符号引用解析为直接引用
对于一个类何时被加载,有且仅有六种情况
1. 遇到new、getstatic、putstatic或者invokestatic
2. 使用反射的时候
3. 初始化时,先初始化父类
4. 虚拟机启动、初始化main方法所在的类
5. 动态语言 java.lang.invoke.MethodHandle
6. 接口中的默认方法、子类初始化的时候,接口也会初始化