虚拟机的类加载机制:虚拟机把描述类的数据从Class文件加载到内存,并对数据及逆行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
1. 类的生命周期
2. 类加载时机
Java虚拟机规范中没有什么时候开始第一阶段加载进行规定,但是规定了5种情况必须进行初始化(加载、验证、准备、解析一定在初始化之前):
- 遇到new、getstatic、setstatic、invokestatic四条指令时。即创建对象、访问类的静态属性(取值或赋值)、执行静态方法时。
- 使用java.lang.reflect包的方法进行反射调用时。
- 当初始化一个类时,发现其父类还没有初始化,则需要先触发父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个类。
- 当使用JDK 1.7的动态语言支持时。
以上都是未初始化才会进行初始化操作。
3. 类加载器
虚拟机设计团队将类加载中加载阶段的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类,实现这个动作的代码模块称为“类加载器”。
3.1 三种系统提供的类加载器
- 启动类加载器(Bootstrap ClassLoader):由C++实现,是虚拟机自身的一部分。负责加载<JAVA_HOME\lib>目录中的,或者被-Xbootclasspath参数指定的路径中的,并且是虚拟机识别的(仅通过名字识别)类库加载到虚拟机内存中。若想让启动类加载器加载类库,在创建类加载器时,将构造函数中parent置为null即可。
- 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME\lib>\ext中的,或者被java.ext.dirs系统变量指定的路径中的类库。
- 应用程序类加载器(Application ClassLoader):是ClassLoader中的getSystemClassLoader中的返回值,所以也称为系统类加载器。负责加载用户类路径(classpath)上的类库。
类加载器之间的层次关系:
这种层次关系称为类加载器的双亲委派模型。要求除了顶层的类加载器外,其余的类加载器都应当有自己的父类加载器,通过组合来复用父类加载器的代码。
工作过程:如果一个类加载器收到了类加载的请求(调用loadClass()
方法),它不会自己去尝试加载这个类,而是把请求委派给父类加载器去完成,每一个层次的类加载器都是如此,所有的加载请求都会传送到顶层的类加载器中,只有当父类反馈无法加载这个请求时,子加载器才会尝试去加载。
3.2 双亲委派模型的好处
对于任意一个类,都需要由它的类加载器和这个类本身一同确定在Java虚拟机中的唯一性,每一个类加载器都拥有一个独立的类名称空间。
两个类比较是否相等,也必须是由同一个类加载器加载的才有意义。相等指的是:类对象Class的equals()
方法、isAssignableFrom()
方法、isInstance()
方法的返回结果,或者使用instanceof
进行对象所属判断进行比较。
系统类的唯一性;应用程序的安全性。
源码:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 先检查请求的类是否已被加载了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父类抛出 ClassNotFoundException
// 说明父类加载器无法完成类加载请求
}
if (c == null) {
// 父类加载器无法加载的时候
// 再调用本身的findClass方法进行类加载
long t1 = System.nanoTime();
c = findClass(name);
// 记录统计数据
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
3.3 破坏双亲委派模型
- 双亲委派模型时JDK 1.2才引入的,以前用户继承java.lang.ClassLoader的唯一目的是为了重写
loadClass()
方法,在1.2之后,不提倡用户重写loadCLass()
方法,应该将自己的类加载逻辑放到findClass()
中,在loadClass()
中如果父类的加载出现异常,则调用自己的findClass()
进行加载。 - 模型的特点使得越基础的类,由上层类加载器去加载,有时候基础类需要调用用户代码。引入“线程上下文类加载器”,线程创建时如果未设置,继承父线程的,如果应用程序全局没有设置过的话,默认为应用程序类加载器。
- 应用程序动态性要求,热部署。
4. 练习
验证由不同的类加载器加载的同一个class文件,生成的Class对象是否“相等”?
思路:
①写一个POJO类,通过控制台执行javac POJO.java
生成类的class文件。
②编写类加载器CustomClassLoader
将class文件加载进来,通过defineClass()
生成Class。
③通过比较Application ClassLoader
和CustomClassLoader
的loadClass()
方法返回的Class是否相等来进行验证。
POJO类:
public class Person {
private String name;
private String sex;
public Person() {
}
public Person(String name, String sex) {
this.name = name;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
CustomClassLoader类:
class CustomClassLoader extends ClassLoader {
/**
* @param name class文件路径,根据该路径读取文件
* @return Class对象
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 判断文件是否存在
File classFile = new File(name);
if (!classFile.exists()) {
try {
throw new FileNotFoundException(name);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
return null;
}
}
// 读取的字节码内容
int fileLength = (int) classFile.length();
byte[] fileBytes = new byte[fileLength];
FileInputStream fis = null;
try {
fis = new FileInputStream(classFile);
fis.read(fileBytes);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Class<?> clazz = defineClass("classLoader.Person", fileBytes, 0, fileLength);
return clazz;
}
}
测试:
public static void main(String[] args) {
// 当前线程的类加载器
ClassLoader applicationClassLoader = Thread.currentThread()
.getContextClassLoader();
// 自定义的类加载器,成员内部类对象创建
ClassLoaderTest classLoaderTest = new ClassLoaderTest();
ClassLoaderTest.CustomClassLoader customClassLoader = classLoaderTest.new CustomClassLoader();
Class<?> personClassA = null, personClassB = null;
try {
personClassA = applicationClassLoader.loadClass("classLoader.Person");
// E:\\Person.class表示class文件存放路径
personClassB = customClassLoader.loadClass("E:\\Person.class");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("equals:" + personClassA.equals(personClassB));
System.out.println("isAssignableFrom:" + personClassA.isAssignableFrom(personClassB));
}
输出结果为:
equals:false
isAssignableFrom:false
5. 参考资料
- 《深入理解Java虚拟机》
- Java 类加载过程
需要pdf资源可以私信。一起学习,加油!