1. 类加载图解
2 . 类的加载过程(加载,验证,准备,解析,初始化)
1 . 加载,将磁盘上的字节码文件load到内存,生成该类的class对象,作为访问该类方法区的入口,触发类的加载常见指令包括getStatic,putStatic,new对象
2 . 验证,验证字节码文件的正确性(比如java类的字节码文件都是cafe babe开头)
3 . 准备,为静态变量赋默认值,基本数据类型默认值,对象类型默认值为null
4 . 解析, 又称静态链接,将静态方法的符号应用替换为直接引用,以为此时静态方法已经加载了,普通方法并没有加载,静态方法的符号引用替换为静态方法所在的内存地址,普通方法的符号引用是在程序运行时动态替换的,此过程成为动态链接
5 . 初始化,对类的静态变量赋值,执行静态代码块,执行构造方法
3 . 类加载器
1 . 类加载器一般分为引导类加载器,扩展类加载器,引用程序类加载器,自定义类加载器
2 . 引导类加载器(BootstrapClassloader)
底层由c++实现的,所有类加载器的父类加载器,负责加载jre核心类库的jar,jre/lib目录下的jar包
3 . 扩展类加载器(ExtClassloader)
java语言实现,继承自URLClassloader,AppClassloader的父类加载器,负责加载jre/lib/ext下的jar包
4 . 应用程序类加载器(AppClassloader)
java语言实现,继承自URLClassloader,自定义类加载器的父类加载器,加载classPath下的jar包
5 . 自定义类加载器
用户自定义实现,继承Classloader类,重写loadclass方法,加载用户自定义类路径下的jar
6 . 类加载器是什么,它是由谁创建的?
BootstrapClassloader,ExtClassloader,AppClassloader统称为系统类加载器,是由sum.misc.Launcher创建的,在Launcher的构造方法中会创建ExtClassloader和AppClassloader,而BootstrapClassloader是c++实现,在java代码中并没有其创建过程
自定义类加载器是由用户创建
Launcher是单例的,保证程序中只有会创建一次并只有一个实例
类加载器关系图
4 . 双亲委派机制
1 . 每种类加载器都有自己的职责加载自己该加载的类
2 . 当程序需要用到某个类时,先在自己负责加载的路径下是否有该类,如果有,去内存中找该类,如果没有,向上委托。父类加载器过程相同,一直到BootstrapClassloader,如果没有,BootstrapClassloader查找是否是自己加载的,如果是,加载该类并返回,如果不是,下放
3 . 双亲委派机制的好处
沙箱安全,保证jdk内部的类不会被修改,即使被修改,也加载不进来
避免重复加载,保证类的唯一性
5 . 全盘负责委托机制
当一个Classloader加载一个类时,该类所引用的类也由该Classloader加载,除非指定用其他类加载器加载
6 . 自定义类加载器实现
继承ClassLoader类,重写findClass方法,自定义classPath路径,加载指定的类
package com.tuling.jvm;
import java.io.FileInputStream;
import java.lang.reflect.Method;
public class MyClassLoaderTest {
static class MyClassLoader extends ClassLoader{
private String classPath;
MyClassLoader(String classPath){
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name+ ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
return defineClass(name, data, 0, data.length);
}catch (Exception e){
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
public static void main(String[] args) throws Exception {
MyClassLoader classLoader = new MyClassLoader("D:/test");
Class clazz = classLoader.loadClass("com.tuling.jvm.User1");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("sout", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
7 . 打破双亲委派机制(加载不同路径下相同全限定名的类)
再创建一个classLoader,再加载一次
public static void main(String[] args) throws Exception {
MyClassLoader classLoader = new MyClassLoader("D:test");
final Class<?> clazz = classLoader.loadClass("com.tuling.jvm.User1");
System.out.println(clazz.getClassLoader().getClass().getName());
final Method sout = clazz.getDeclaredMethod("sout");
final Object obj = clazz.newInstance();
sout.invoke(obj, null);
System.out.println("-------------------");
MyClassLoader classLoader1 = new MyClassLoader("D:test1");
final Class<?> clazz1 = classLoader1.loadClass("com.tuling.jvm.User1");
System.out.println(clazz1.getClassLoader().getClass().getName());
final Method sout1 = clazz1.getDeclaredMethod("sout");
final Object obj1 = clazz1.newInstance();
sout1.invoke(obj1, null);
}
8 . tomcat是怎么实现多个项目依赖不同版本jar包
在tomcat中,可以同时跑两个war包,两个war包可能分别依赖于apring4和spring5,而spring4和spring5中存在很多同名的包,用上面的方法就可以实现加载同名的包
附tomcat的类加载器图例