文章目录
在冯.诺依曼定义的计算机模型中,任何程序都需要加载到内存才能与 CPU 进行交流
字节码
.class
文件同样需要加载到内存中,才可以实例化类。
在加载类时, 使用的是 Parents Delegation Model
,译为 双亲委派模型
一、类加载器作用
Java 的类加载器是一个运行时核心基础设施模块。
主要是在启动之初进行类的 加载(Load)、链接(Link)、初始化(Init)
具体如图:
(1)加载 Load
Load 阶段读取类文件产生二进制流, 并转化为特定的数据结构,初步校验 cafe babe
魔法数、常量池、文件长度、是否有父类等, 然后创建对应类的 java.Jang.Class
实例
(2)链接 Link
Link 阶段包括验证、准备、解析三个步骤。
- 验证是更详细的校验,比如
final
是否合规、类型是否正确、静态变量是否合理等 - 准备阶段是为静态变量分配内存,并设定默认值。
- 解析类和方法确保类与类之间的相互引用正确性,完成内存结构布局。
(3)初始化 Init
Init 阶段执行类构造器<clinit>
方法,如果赋值运算是通过其他类的静态方法来完成的, 那么会马上解析另外个类,在虚拟机枪中执行完毕后通过返回值进行赋值。
二、类加载详情
类加载是一个将 .class
字节码文件实例化成 Class
对象并进行相关初始化的过程。
在这个过程中, JVM 会初始化继承树上还没有被初始化过的所有父类,并且会执行这个链路上所有未执行过的静态代码块、静态变量赋值语旬等。
(1)双委托模型
如图:
作用:为了避免重复加载,由下到上逐级委托,由上到下逐级查找
- 首先不会自己去加载类,而是把这个请求委托给父加载器去完成
- 只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载。
1)启动类加载器
Bootstarp ClassLoader
负责加载机器上安装的
Java
目录下的核心类
JVM
启动后,首先依托启动类加载器,去加载环境下lib
目录中的核心类库
2)扩展类加载器
Extension ClassLoader
加载 lib/ext
目录下
3)应用程序类加载器
Application ClassLoader
去加载ClassPath
环境变量所指定的路径中的类
其实,就是去加载自己写的Java
代码,这个类加载负载将那些类加载到内存中。
4)自定义类加载器
自定义类加载器,根据需求去加载你的类。
(2)举个栗子
输出
三、类卸载
类什么时候会被卸载?
满足以下两个条件:
- 该
Class
所有的实例都已经被GC
- 加载该类的
ClassLoader
实例已经被GC
可以通过jvm -verbose:class
输出类加载和卸载的日志信息
四、类热加载
ClassLoader
和 Class
确定一个 类
当.java
重新编译为.class
文件
通过创建新的类加载器来实现热加载。
五、问题
(1)如何突破双亲模式?
可以通过重载 ClassLoader
来修改双亲委托模式。
即,重写
loadClass
方法,来改变类的加载次序。
比如:先使用自定义类加载器加载,如果加载不到,则交给双亲加载。
实现方案:
- 使用线程上下文类加载器实现,让父类加载器请求子类加载器去完成类加载的动作。
- 直接创建,然后去加载
(2)JVM
如何知道类在哪?
可以查看openjdk
源码:sun.misc.Launcher.AppClassLoader
即,读取java.class.path
配置,指定去哪些地址加载类资源。
验证下:
-
jps
查看本机 Java
进程 - 查看运行时配置:
jcmd 进程号 VM.system_properties
Java里天生可以动态扩展的语言特性就是依赖运行期动态加载和动态连接。(Tips:例如AOP(动态代理),因为Java是静态,不像Ruby Python 运行时修改源码,Java不行,Java只能修改字节码来实现运行时动态。即根据Class,读取字节码,进行修改,再形成字节数组,写入内存或文件,从而实现。)
其他资料:Javasisit,ASM。