一、类加载机制
类加载阶段:虚拟机要完成的3件事情:
1)通过一个类的完全限定名来 获取 此类的二进制字节流
2)将这个字节流所代表的静态 存储结构转化为方法区的运行时 数据结构
3)在 内存生产java.lang,Class对象,作为 换个类的各种数据访问入口。
这个加载过程是由类加载器完成,具体来说,是由ClassLoader子类,BootStrapClassLoader(启动类加载器)、ExtClassLoader(扩展类加载器)、AppClassLocader(系统加载器)。类加载器本身也是一个类,实质就是把类文件从硬盘读取到内存中。其中BootSrapCLassLoader 加载的目录为 JRE/lib/rt.jar(用来加载Java核心类库,无法被Java程序员直接引用) 。ExtClassLoader 加载目录为JRE/lib/ext(用来加载Java的扩展库,Java虚拟机的实现会提供一个扩张类库),appClassLoader 加载目录为classPath。
二、类的加载方式由显示加载和隐式加载:
显示加载:是通过调用Class.forName()方法来把所需的类加载到JVM中
隐式加载:是程序在使用new等方式创建对象时,会隐式的调用类的加载器把对应的类加载器加载到JVM中
三、双亲委派
双亲委派制就是当加载一个Class文件时会先交由上层ClassLoader来加载,如果发现已加载则直接返回,如果没有加载则去当前ClassLoader 的classes目录寻找该Class文件,找到则加载,找不到则交由下层ClassLoader来继续加载,如果直到最下层加载器都无法加载(找不到该Class文件)则抛出ClassNotFoundException异常。
为什么需要双亲委派模型?
为了保证相同的class文件,在使用的时候,是相同的对象,jvm设计的时候,采用了双亲委派的方式来加载类。
为什么需要破坏双亲委派?
因为在某些情况下父类加载器需要委托子类加载器去加载class文件。
受到加载范围的限制,父类加载器无法加载到需要的文件,以Driver接口为例,由于Driver接口定义在jdk当中的,而其实现由各个数据库的服务商来提供,比如mysql的就写了MySQL Connector,那么问题就来了,DriverManager(也由jdk提供)要加载各个实现了Driver接口的实现类,然后进行管理,但是DriverManager由启动类加载器加载,只能加载JAVA_HOME的lib下文件,而其实现是由服务商提供的。
由系统类加载器加载,这个时候就需要启动类加载器来委托子类来加载Driver实现,从而破坏了双亲委派,这里仅仅是举了破坏双亲委派的其中一个情况。
破坏双亲委派
需要保留双亲委派模型:extends ClassLoader,重写 findClass()
破坏双亲委派模型:直接重写 loadClass()
四、类加载过程
加载
通过类的全路径将这个类从外部加载到jvm中,同时在方法区生成该类的描述信息并在内存生成该类的Claas类型。作为方法区这个类的数据访问入口。
验证
字节码格式验证,如对jvm是否有害,释放符合当前虚拟机的要求 。
准备
为类的静态变量分配内存并根据这个静态变量所属的数据类型进行初始化。
解析
将符号引用替换成直接引用
初始化
在初始化阶段会调用类的初始化方法clinit()为静态变量赋予实际的值(例如将value赋值为123)、执行静态代码块。在 JVM 规范中没有强制约束加载的时机,不过对于初始化,JVM规范严格规定了有且只有5种情况必须立即对类进行初始化:
下面我们讲解 一下clinit()方法是怎么生成的。clinit()方法是编译器 自动收集类中的静态变量和静态语句所产生的。编译器收集的顺序是由语句出现的顺序 所 决定的,静态语句块只能复制定义在它后面的变量,但是不能使用,如下 图 所示,而且虚拟机规范保证,父类的clinit()方法一定 在子类之前执行,但不是 通过继承来的。
四、自定义类加载
public class CustomerClassLoaderTest extends ClassLoader{
private final String classDir;
public CustomerClassLoaderTest(String classDir) {
this.classDir = classDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String fileName = name;
if (fileName.indexOf('.') != -1) {
fileName = fileName.replaceAll("\\.", "\\\\");
}
fileName = fileName + ".class";
try {
try (FileInputStream in = new FileInputStream(classDir + fileName)) {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len = 0;
while ((len = in.read(buffer)) != -1) {
out.write(buffer,0,len);
}
byte[] data = out.toByteArray();
return defineClass(name, data, 0, data.length);
}
}
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
}