java类加载机制
什么是类加载机制?
我们编写的java文件是如何加载到JVM运行的
java文件----->javac命令----->class文件----java命令----
当调用java命令执行class文件就会加载类,也就是如下三个过程
类加载主要分为如下三个过程
加载过程 在硬盘上查找并通过IO流读入字节码进JVM内存结构的方法区同时在堆上形成class对象
连接过程
验证 校验字节码文件的正确性
准备 为类的静态变量分配内存初始化为默认值,对于final static变量编译就分配了
解析 将类中的符号引用转换为直接引用
初始化过程 对类的静态变量初始化为指定的值 执行静态代码块
类加载器主要分为以下四类
不同的加载器加载不同的class文件
引导类加载器 负责加载jre/lib目录下的核心类库 类似rt.jar等jar包
扩展类加载器 负责加载jre/lib/ext目录中的IAR类包
应用类加载器 负责加载ClassPath路径下的class字节码文件 主要就是自己写的类
自定义加载器 负责加载用户自定义路径下的class字节码文件 (路径自己定义)
类加载器
产生过程
本篇文章针对windows系统的类加载过程
第一步java会调用底层C++代码创建JVM虚拟机
创建一个引导类加载器实例C++实现
调用C创建JVM启动器实例sun.misc.Launcher类
该类初始化的时候会创建两种类加载器
扩展类加载器
应用类加载器
源码如下
//构造器
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//创建 扩展类加载器
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
...
}
try {
//创建 应用类加载器 将扩展类加载器传入作为应用类加载器的parent属性
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
...
}
获取运行类自己的加载器,默认是AppClassLoader加载器
ClassLoader c = Launcher.getClassLoader()
c.loadClass("...xxx.class")加载类
加载完成后执行main方法入口
运行
JVM销毁
三种类加载器的关系
应用类加载器的父加载器是扩展类加载器
扩展类加载器的父加载器是引导类加载器
双亲委派机制
双亲委派机制是类加载过程中实现的一种加载方法
当第一次加载类的时候
应用类加载器加载类不会直接去classPath路径下找class文件,而是会交给自己的父加载器(parent)
向上委托给扩展类加载器,而扩展类加载器仍然不会直接去ext路径下查找class文件,而是会交给
引导类加载器(C++实现),此时引导类加载器会去jre/lib目录下查找class文件,找不到返回null
找到直接加载,如果返回null则此时交给扩展类加载器处理,它会去ext路径下查找class文件,同理
找到加载,找不到返回null交给应用类加载器去classPath路径下加载,有则加载,没有则报错。
为什么要采用这种类加载方法?
沙箱安全机制保证jdk核心代码加载完毕
可以避免类重复加载
为什么不直接从引导类加载器向下加载?
大部分业务类是由应用类加载器加载 第一次加载慢 之后加载会很快
loadClass()方法
功能:加载类
类加载过程中 ClassLoader的loadClass()是非常重要的
如下源码演示 c代表 因为引导类加载器是C++实现的 所以扩展类加载器的parent为空
Class<?> c = findLoadedClass(name);//如果是应用类,第一次c为null 第二次就直接返回
if (c == null) {
//判断parent是否为空 应用类加载器的parent就是扩展类加载器,在初始化Launcher的时候指定的
if (parent != null) {
//有父加载器就调用
//应用类加载器---->扩展类加载器
c = parent.loadClass(name, false);
}else {
//扩展类加载器 ---> 引导类加载器
c = findBootstrapClassOrNull(name);
}
//没有找到则向下调用findClass方法 扩展类加载器->应用类加载器
if (c == null) {
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
findClass()方法
功能:获取class对象
该方法由URLClassLoader类实现 关键代码如下
name就是类路径例如("com.nicky.classloader.TestClass")
res为字节数组
return defineClass(name, res);
返回class对象 Class<?>类型交给loadclass方法
如何自定义类加载器?
根据源码ClassLoader里的例子演示
如果不想破坏双亲委派机制 直接重写findclass方法自定义类加载路径即可
如果想破坏双亲委派机制,就需要重写loadClass方法将关键代码修改
NickyClassLoader.java 自定义类加载器
破坏了双亲委派机制 重写loadclass方法
因为双亲委派机制会在第二次加载同类名的时候直接加载
例如Tomcat部署多个war包的情况下,2个不同的war包会有相同的类名
双亲委派机制则会导致第二个加载的war包不能加载到自己war包的内容
所以如果是java的包仍然采用双亲委派机制,自定义的包就必须得破坏双亲委派机制
package com.nicky.classloader;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
/**
* 自定义类加载器
*/
public class NickyClassLoader {
static class MyClassLoader extends ClassLoader {
//破坏双亲委派机制
@Override
public Class<?> loadClass( String name ) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
//java自己的类 传递给上层加载器加载
if (!name.startsWith("com.nicky")){
c = super.getParent().loadClass(name);
}else{
c = findClass(name);
}
}
return c;
}
}
private String classpath;
public MyClassLoader( String classpath ) {
this.classpath = classpath;
}
//可以自定义classloader加载class文件
@Override
protected Class<?> findClass( String name ) throws ClassNotFoundException {
try {
byte[] data = getByte(name);
//将一个字节数组转为class对象 这个字节数组是class文件读取后最终的字节数组
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
private byte[] getByte( String name ) throws IOException {
name = name.replace(".", "/");
FileInputStream fileInputStream = new FileInputStream(classpath + "/" + name + ".class");
int len = fileInputStream.available();
byte[] bytes = new byte[len];
fileInputStream.read(bytes);
fileInputStream.close();
return bytes;
}
}
//tomcat 如何实现 多个项目自定义类加载
public static void main( String[] args ) throws Exception {
MyClassLoader classLoader = new MyClassLoader("E:/test");
Class<?> aClass = classLoader.loadClass("com.nicky.classloader.TestClass");
Object o = aClass.newInstance();
Method say = aClass.getDeclaredMethod("say");
say.invoke(o);
MyClassLoader classLoader1 = new MyClassLoader("E:/test1");
Class<?> aClass1 = classLoader1.loadClass("com.nicky.classloader.TestClass");
Object o1 = aClass1.newInstance();
Method say1 = aClass1.getMethod("say");
say1.invoke(o1);
}
}
两个不同的TestClass.class文件
一个路径在
E:\test\com\nicky\classloader\TestClass.class
另一个在
E:\test1\com\nicky\classloader\TestClass.class
两个文件是两次编译形成
public void say(){
System.out.println("1111111");
}
public void say(){
System.out.println("2222222");
}
最后打印效果如下
根据个人结合源码以及他人视频总结,如有错误望指正修改,谢谢大家。