=类加载过程=
加载、链接、初始化
链接阶段又分为:验证、准备、解析三个阶段。
1.加载阶段
将class文件读入内存,生成一个大的class对象
分为两块:
一块是将Class文件的二进制内容放到了内存中;
一块是生成了一个Class对象,Class对象指向了内存中的二进制内容
2.链接
验证:校验装进来的文件符不符合class文件的标准。验证过程主要包括:文件格式验证、元数据认证、字节码认证、符号引用认证。
准备:静态变量(类变量)赋默认值
解析:将常量池里面的符号引用转换为直接引用(也就是转换成内存地址),使以后能够直接使用。
符号引用可以理解为就是一个占位符,直接引用可以理解为真实的内存地址。
3.初始化
调用类初始化代码,静态变量赋初始值
类加载器
类加载器用于将class字节码文件加载到内存中,生成一个大的class对象。
分为4种:
启动类加载器:负责加载一些核心的jar,lib路径下面的jar包,例如String。
扩展类加载器:负责加载扩展的jar,例如jre/lib/ext下的jar包。
应用程序类加载器:负责加载用户类Classpath指定的内容,我们自己写的类就由它进行加载。
自定义类加载器常用于应用服务器等场景,用于允许不同的模块使用自己的类加载器,加载、隔离同名类。
自定义类加载器的实现:继承ClassLoader类,实现findClass方法。
应用场景:web容器,如tomcat,每个web应用有自己的类加载器进行加载,防止类冲突。
热部署技术通过使用自定义类加载器支持了类的动态加载。
jdk9之后没有扩展类加载器了,变成了平台类加载器(PlatformClassLoader),主要加载module-info.java
中定义的类。原来的lib/ext下的jar包,被拆分成了jmod文件。
==双亲委派机制=
当一个类加载器试图加载某个类的时候,会先将加载请求向上委派给父类加载器,父类加载器再向上委派给它的父类,直到启动类加载器,然后再逐层往下,启动类加载器找不到,再去扩展类加载器找,找不到就去应用程序类加载器找。
底层实现:
底层代码loadClass是使用的递归实现的。先调用findLoadedClass看缓存里面有没有,
找到直接返回,没找到,调用parent.loadClass函数,从父加载器的缓存里面找,如果一直没有找到,
则由上往下调用findClass去真正的加载class。如果到达了最底层,最终没有找到,则返回ClassNotFoundException。
为什么要有双亲委派机制?
主要是为了安全,防止类重复加载。
例如自己定义一个java.lang.string,如果没有双亲委派,就可以覆盖核心类,自定义的string就可以做一些坏事情。
有了双亲委派,就会先去上层找有没有被加载过,由于string类是由Bootstrap加载,加载过就会直接返回,
没加载过就会进行加载,但是加载的是核心类库的string,而不是自定义的String类。
破坏双亲委派的例子:
1.tomcat可以加载同一类库的不同版本。例如Tomcat将应用丢到一个目录,就能加载跑起来。
如果有两个应用,都使用了Junit,一个用的是JUnit4,一个是Junit5,
这时候Tomcat就打破了双亲委派,加载了两个不同版本的类库。
2.JDBC。jdbc的接口类库定义的,由启动类加载器加载。但是实现各个厂商都不一样,启动类加载器是找不到这些实现的类的,
只能由应用程序类加载器或者自定义加载器加载。默认情况下,一个类及其依赖类由同一个类加载器加载。所以也打破了双亲委派机制。
如何打破?
继承ClassLoader类,重写loadClass打破双亲委派
原理:类加载器在进行类加载的时候,它首先不会自己去尝试加载这个类,
而是把这个请求委派给父类加载器去完成(调用父加载器 loadClass()方法来加载类)。
所以可以直接在loadClass里面直接加载对应的类进行返回,从而打破双亲委派机制。
类卸载的条件:
类卸载的条件比较苛刻,只有当一个类不再被任何引用所指向时,即该类所有的实例都已被回收,并且加载该类的ClassLoader也已经被回收,该类才可能被卸载。
1767

被折叠的 条评论
为什么被折叠?



