本文这三章的笔记整理。
类加载的过程可以简单分为三个阶段:
JVM
规范规定了每个类或接口在首次主动使用的时候都需要进行初始化,规定了以下六种主动使用类的场景:
new
关键字会导致类的初始化main()
的类)也会初始化除了以上六种情况外,其余的都叫被动使用,不会导致类的加载和初始化,比如引用类的静态常量不会导致类的初始化。
前面也说了类加载可以简单分为三个阶段:
下面先来看一下加载阶段。
加载阶段就是将class
文件中的二进制数据读取到内存之中,然后将该字节流代表的静态存储结构转换为方法区中运行时数据结构,并且在堆中生成一个该类的java.lang.Class
对象,作为访问方法区数据结构的入口。
类加载的最终产物就是堆内存中的class
对象,JVM
规范中指出类加载是通过一个全限定名去获取二进制数据流,来源包括:
class
文件:这是最常见的格式,就是加载javac
编译后的字节码文件ASM
可以动态生成,或者可以通过动态代理java.lang.Proxy
生成等RMI
JAR
、WAR
包MySQL
中的BLOB
字段类型的数据class
文件并且动态加载:比如Thrift
、Avro
等序列化框架,将某个schema
生成若干个class
文件并进行加载类加载阶段结束后,JVM
会将这些二进制字节流按照JVM
定义的格式存放在方法区中,形成特定的数据结构后再在堆内存中实例化一个java.lang.Class
对象。
该阶段可以分为三个小阶段:
需要注意的是这三个小阶段其实不是顺序进行的,而是交叉着进行的,也就是解析的时候其实也会有验证的过程。
验证是为了确保字节流所包含的内容符合JVM
规范,并且不会出现危害JVM
自身安全的代码,当字节流信息不符合要求的时候,会抛出VerifyError
这样的异常或其子异常,验证的信息包括:
包括:
0xCAFEBABE
)元数据验证其实是进行语义分析的过程,语义分析是为了确保字节流符合JVM
规范要求,包括:
final
的类字节码验证主要是验证程序的控制流程,包括:
验证符号引用转换为直接引用的合法性,保证解析动作的顺利执行,包括:
经过验证后,就开始了准备阶段,这阶段比较简单,就是对对象的静态变量分配内存并且设置初始值,类变量的内存会被分配到方法区中。设置初始值就是为相应的类变量给定一个相关类型在没有被设置时的默认值,比如Int
的初始值为0,引用的初始值为null
。
解析就是在常量池中寻找类、字段、接口和方法的符号引用,并且将这些符号引用替换成直接引用的过程。解析主要针对类接口、字段、类方法和接口方法进行的,包括:
初始化阶段主要就是执行<clinit>
方法的过程,该方法是编译阶段生成的,也就是说包含在字节码文件中,该方法包含了所有类变量的赋值动作和静态语句块的执行代码。另一方面,<clinit>
与构造方法不同,不需要显式调用父类构造器,虚拟机会保证父类的<clinit>
方法最先执行。
还需要注意的是<clinit>
只能被虚拟机执行,虚拟机还会保证多线程下的安全性,因此,如果在静态代码块中如果包含了加载其他类的操作可能会引起死锁,例子可以看这里。
JVM
中的三类核心类加载器JVM
中有三类核心类加载器,分别是:
C++
编写,负责JVM
核心类库的加载,比如加载整个java.lang
包中的类jre/lib/ext
子目录下的类库,纯Java
实现,是URLClassLoader
的子类classpath
下的类库,应用类加载器的父加载器为扩展类加载器,同时它也是自定义类加载器的默认父加载器一个类加载器加载一个类的时候,并不会尝试直接加载该类,而是先交给父加载器尝试加载,一直到顶层的父加载器(启动类加载器),如果父加载器加载失败,则会自己尝试加载,图示如下:
JDK
中提供了很多SPI
(Service Provider Interface
),比如JDBC
等,JDBC
只规定了这些接口之间的逻辑关系,但不提供具体的实现,换句话说,JDBC
完全透明了应用程序和第三方厂商数据库驱动的具体实现,应用程序只需要面向接口编程即可。但问题是:
java.lang.sql
中的所有接口都是由JDK
提供的,加载这些接口的类加载器是启动类加载器由于双亲委派机制,Connections
、Statement
等都是由启动类加载器加载,而第三方JDBC
驱动包中的实现不会被加载。解决这个问题的关键,就是使用了线程上下文类加载器打破了双亲委派机制。
比如MySQL
驱动的加载过程,就是通过线程上下文类加载器加载的,
private static Connection getConnection(String url, Properties info, Class<?> caller) throws SQLException {
//...
if (callerCL == null || callerCL == ClassLoader.getPlatformClassLoader()) {
callerCL = Thread.currentThread().getContextClassLoader();
}
while(true) {
//...
if (isDriverAllowed(aDriver.driver, callerCL)) {
}
}
//...
}
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
//...
try {
aClass = Class.forName(driver.getClass().getName(), true, classLoader);
} catch (Exception var5) {
result = false;
}
//...
return result;
}
通过线程上下文类加载器,就变成了启动类加载器去委托子类加载器去加载实现的方式,也就是JDK
自己亲自打破了双亲委派机制这种方式,这种加载方式几乎涉及所有的SPI
加载,包括JAXB
、JCE
、JBI
等。
还记得我在《2020 年 JavaScript 状态调研报告小结》中提到的 2020 年全球开发者...
macromedia dreamweaver 8序列号 激活码: wpd800-50438-28032-39991 wpd800-599...
Dreamweaver网页中的banner图片需要切换,我们可以添加按钮来切换图片,下面我们...
在开发中,如果遇到需要使用canvas同时绘制多张图片,但因为图片大小的不一样,...
随着网络时代的发展与进步,我们的学习工作和生活早已离不开互联网,智能家居、...
windows 下默认的滚动条样式巨丑,项目中又有比较多地方会显示滚动条, 故回头翻...
dreamweaver软件: 点此下载 1、熟悉网页设计的网友就知道,调用STYLE的方法很多...
这是一款基于HTML5和JavaScript的进度条应用,这款进度条插件非常有特点,它在进...
1、使用css精灵。 好处是将css中使用的小图片可以合并为一张大图片减少了对服务...
Do not use these html elements in html pages. Presentational elements shoul...