JVM类加载机制
2019-02-12 本文已影响8人
黄靠谱
概述
class物理文件加载到JVM内存中,可以有多种加载方式:
- 3种JDK自带的ClassLoader.
- 自定义的ClassLoader(重写ClassLoader)
- 反射、JDK动态代理、CGLIB也可以实现运行期编译生成一个新的java对象
类的加载器和核心类
- 核心类加载器
- BootstrapClassLoader 最顶层的加载类,负责加载lib下的rt.jar、resources.jar、charsets.jar和class等
- ExtentionClassLoader 加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件
- AppClassLoader 加载当前应用的classpath的所有类
- 自定义ClassLoader,可以突破class文件包路径的限制,可以修改类加载机制(比如热加载),需要重写findClass()缓存查找方法制定寻找路径即可
- 加载的特点
- 不同包路径下的类由不同的类加载器加载
- jvm懒加载,根据需要去动态加载
- 父加载器不是父类,双亲委托:自下而上,先挨个找缓存,到了顶层缓存中还没有,就开始初始化,从各自对应负责的包路径下查找,有就创建,没有给就子加载器加载
- 加载之后存在缓存当中
- 加载的结果
- 将class文件加载在内存中。
- 将静态数据结构(数据存在于class文件的结构)转化成方法区中运行时的数据结构(数据存在于JVM时的数据结构)。
- 在堆中生成一个代表这个类的java.lang.Class对象,作为数据访问的入口。
- 核心方法: loadClass、 findLoadedClass、parent.loadClass、findBootStrapClass、findClass
自定义类加载器
实现自定义class文件加载路径,实现,class文件包路径的限制
https://www.cnblogs.com/gdpuzxs/p/7044963.html
实现逻辑:写一个classloader类(构造函数里面需要传入包真实的路径),重写findClass方法,
public class MyClassLoader extends ClassLoader{
public MyClassLoader(String classpath) {
this.classpath = classpath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte [] classDate=getDate(name);
if(classDate==null){}
else{
return defineClass(name,classDate,0,classDate.length);
}
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
}
实现类的每次重新加载,实现热加载
- 本来重写 findLoadedClass()即可,直接返回null,实现热加载,但是ClassLoader把该方法设置为final了,不希望去破坏这个规则
- 所以直接通过重写过的findClass来实现,相当于在上面的基础上做修改,findClass的路径为当前路径,每次loadClass,直接调用findClass方法即可,虽然只能修改本地的class类
- findClass需要defineClass来最终把字节码文件加载进内存当中,JVM默认相同的类加载器不能加载相同全路径的类,所以每次重新加载的时候需要new一个自定义加载器,否则会报错
String currentPath=Class.class.getClass().getResource("/").getPath()
MyClassLoader myClassLoader=new MyClassLoader(currentPath);
Class c=myClassLoader.findClass("com.test.Action.Test");
MyClassLoader myClassLoader2=new MyClassLoader(currentPath);
Class c2=myClassLoader2.findClass("com.test.Action.Test");
tomcat的类加载模型
- tomcat本身也是一个依赖JDK的应用
- tomcat/bin目录下有自己的jar包需要AppClassLoader加载:tomcat-juli.jar、bootstrap.jar等等
- 每个独立的应用有自己的class路径,通过WebappClassloader加载,应用也依赖JDK的加载,所以应用的类加载机制:缓存加载-->AppClassLoader JDK系统加载-->自己加载-->父类加载
- 核心加载器
- CommonClassLoader(默认父加载器为AppClassLoader)。加载一些tomcat/lib下面的jar包,servlet-api.jar、jsp-api.jar等
- catalinaClassLoader(默认是空,有需要的话需要自己配置)
- sharedClassLoader(默认是空,有需要的话需要自己配置)
- WebAppClassLoader(tomcat下 WebRoot/应用程序/WEB-INF/lib 和 class包下的类
- 层级结构
- tomcat下每个应用程序都有一个独立的类加载器,WebAppClassLoader,因为不同应用程序的类不能乱加载,更不能公用啊。
- 但是有一些通用的类,比如JUnit、Log4j类,是不同应用程序公用的类,所以有sharedClassLoader出场,可以设置路径来实现这个类加载器,从而实现不同应用程序共同加载一些通用工具类
- 再上一层就是CommonClassLoader:下面由shardClassLoader和CatalinaClassLoader,一个是所有应用程序,一个是tomcat容器的扩展类加载器
- 再上一层就是AppClassLoader了,就是传统的模式往上走了
- 加载逻辑(源码 org.apache.catalina.loader.WebappClassLoader#loadClass)
tomcat的类加载机制是违反了双亲委托原则的:先本地缓存查找,再全局缓存查找,再系统加载,再自己加载,最后再父加载器加载
- 先在本地缓存中查找是否已经加载过该类(clazz = findLoadedClass0(name))
- 再查询JVM缓存中查找是否已经加载过该类( clazz = findLoadedClass(name))
- 让系统类加载器(AppClassLoader)尝试加载该类,主要是为了防止一些基础类会被web中的类覆盖(tomcat bin下包)(clazz = system.loadClass(name))
- web应用的类加载器将自行加载(findClass),这里也违背了双亲 (clazz = findClass(name);)
- 最后还是加载不到的话,则委托父类加载器(Common ClassLoader)去加载。(clazz = Class.forName(name, false, parentLoader);)
反射和Spring IOC
- 反射是运行期编译,只有执行到这个方法,确定了参数:类名的时候才编译出对应的class文件,再加载到内存当中
Class tc = Class.forName("com.java.dbtest.TestConnection"); - CGLIB也是运行期编译,Enhancer类继承代理类,拦截相应的方法,实现代理