JVM学习系列:类的加载(加载阶段)
概述
Java中,如果想要运行一段代码,那么就需要先将编写完的.java文件编译成.class文件,再通过加载机制将该.class文件加载到JVM内存中。这里就涉及到了类的加载,一个完整的类加载过程包括:加载、连接(验证、准备、解析)、初始化。需要注意的是,此处的加载仅仅是整个加载流程的第一步,本文也仅仅关注于该步骤。
在加载阶段,主要完成了三件事:
- 通过类的全限定类名找打对应的字节码文件
- 将字节码文件中定义的静态存储结构转换成方法区的运行时数据
- 在堆中生成相应的Class对象,作为访问方法区中这个类的各种数据的入口
![image]()
类加载器
在Java中,提供了一下三个类加载器:
- BootstrapClassLoader:引导类加载器,由C++实现,主要用于加载核心类库,包括:jre/lib/resources.jar、jre/lib/rt.jar、jre/lib/sunrsasign.jar、jre/lib/jsse.jar、jre/lib/jce.jar等。
- ExtClassLoader:扩展类加载器,继承自ClassLoader,主要用于加载jre/lib/ext目录下的类。
- AppClassLoader:系统类加载器,继承自ClassLoader,主要用于加载当前应用CLASSPATH指定的路径下的类,用户编写的类基本都是通过改加载器进行加载到JVM中的。
如果有兴趣继续深入的话,可以研究一下sun.misc.Launcher这个类,该类主要负责创建ExtClassLoader和AppClassLoader。
双亲委托机制
既然Java提供了3个类加载器,那么这三个加载器之间是怎么协作的呢?JVM中采用的是双亲委托机制。
原理
某个加载器在接收到类加载请求时先查看这个类是否已经加载了,如果没有就将这个加载请求推送至父加载器,由父加载器进行处理。直到最顶层的BoostrapClassLoader,因为再也没有父加载器了,那就由其本身尝试加载,如果无法加载,那就从上到下一次推给子加载器进行加载,如果到最底层的AppClassLoader(此处不考虑用户自定义类加载器)依旧无法加载该类,那么就抛出ClassNotFoundException。
ClassLoader类中的loadClass方法就是双亲委托机制的实现。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized(this.getClassLoadingLock(name)) {
//查找该类是否已经被加载
Class<?> c = this.findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (this.parent != null) {
//如果父加载器存在,则交由父加载器加载该类
c = this.parent.loadClass(name, false);
} else {
/*父加载器不存在,则交由BootstrapClassLoader加载。
此处的父加载器为null的原因在于ExtClassLoader的父加载类
BootstrapClassLoader是由C++编写的,所以虽然ExtClassLoader
以其为父加载器,但是返回结果为null
*/
c = this.findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException var10) {
}
//如果父加载器没有加载到该类,那就由本加载器负责进行加载
if (c == null) {
long t1 = System.nanoTime();
c = this.findClass(name);
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
this.resolveClass(c);
}
return c;
}
}
优点
双亲委托机制中,委托是从下层加载器到上层加载器,查找路径则是从上层加载器的加载范围到底层加载器的加载范围。
从下到上的委托,避免了出现重复加载一个类的问题,而从上到下的查找路径避免了java核心类被用户自己定义的同名类替换的问题。
自定义加载器
JVM中,除了使用默认提供的3个加载器,还可以自定义类加载器。这种自定义的类加载器有什么作用呢?
- 可以指定从对应的磁盘路径或网络中加载类
- 可以对相应的.class文件进行加密,并通过特定解密加载器对其进行加载
- 从专门的数据库中加载类
那么,如何实现自定义的类加载器呢?可以通过继承ClassLoader类,并重写findClass(String name)方法实现一个自定的类加载器。
以一个从指定位置进行查找的类加载器为例。
被加载类
准备一个待加载的类,将它放到磁盘中的任意位置。
import java.lang.*;
public class Test{
public void say(){
System.out.println("hello world");
}
}
编写自定义加载器
import java.io.*;
public class DiskClassLoader extends ClassLoader{
private final String path;
protected DiskClassLoader(String path) {
super();
this.path = path;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if(name!=null && !"".equals(name)){
String classFilePath = getClassFilePath(name);
File file = new File(classFilePath);
FileInputStream fileInputStream =null;
ByteArrayOutputStream byteArrayOutputStream=null;
try {
fileInputStream = new FileInputStream(file);
byteArrayOutputStream = new ByteArrayOutputStream();
int len = 0;
try{
while((len=fileInputStream.read())!=-1){
byteArrayOutputStream.write(len);
}
byte[] bytes = byteArrayOutputStream.toByteArray();
//生成Class对象
return defineClass(name,bytes,0,bytes.length);
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
throw new ClassNotFoundException();
} finally {
if(fileInputStream!=null){
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(byteArrayOutputStream!=null){
try {
byteArrayOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}else{
throw new ClassNotFoundException();
}
return null;
}
private String getClassFilePath(String name) {
int index = name.lastIndexOf(".");
String classSimpleName = name.substring(index+1);
return path+(path.endsWith("/")?"":"/")+classSimpleName+".class";
}
}
加载类并执行方法
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test01 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException,
IllegalAccessException, NoSuchMethodException, InvocationTargetException {
System.out.println(System.getProperty("java.class.path"));
//Test.class文件被放到了/home/ttdtc/Desktop
DiskClassLoader diskClassLoader = new DiskClassLoader("/home/ttdtc/Desktop");
//此处填写的应该是类的全限定类名,但是类Test没有指定package,所以只填Test
Class<?> clazz = diskClassLoader.loadClass("Test");
if(clazz!=null){
Object obj = clazz.newInstance();
Method say = clazz.getDeclaredMethod("say");
say.invoke(obj);
}else{
throw new ClassNotFoundException();
}
}
}
执行结果
通过自定义的类加载器DiskClassLoader,成功加载了Test类,执行了say方法

结束语
初学JVM,如有出错欢迎纠正,谢谢!


浙公网安备 33010602011771号