JVM类加载机制

概述

class物理文件加载到JVM内存中,可以有多种加载方式:

  • 3种JDK自带的ClassLoader.
  • 自定义的ClassLoader(重写ClassLoader)
  • 反射、JDK动态代理、CGLIB也可以实现运行期编译生成一个新的java对象

类的加载器和核心类

  1. 核心类加载器
  • BootstrapClassLoader 最顶层的加载类,负责加载lib下的rt.jar、resources.jar、charsets.jar和class等
  • ExtentionClassLoader 加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件
  • AppClassLoader 加载当前应用的classpath的所有类
  • 自定义ClassLoader,可以突破class文件包路径的限制,可以修改类加载机制(比如热加载),需要重写findClass()缓存查找方法制定寻找路径即可
  1. 加载的特点
  • 不同包路径下的类由不同的类加载器加载
  • jvm懒加载,根据需要去动态加载
  • 父加载器不是父类,双亲委托:自下而上,先挨个找缓存,到了顶层缓存中还没有,就开始初始化,从各自对应负责的包路径下查找,有就创建,没有给就子加载器加载
  • 加载之后存在缓存当中
  1. 加载的结果
  • 将class文件加载在内存中。
  • 将静态数据结构(数据存在于class文件的结构)转化成方法区中运行时的数据结构(数据存在于JVM时的数据结构)。
  • 在堆中生成一个代表这个类的java.lang.Class对象,作为数据访问的入口。
  1. 核心方法: 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);
    }
}

实现类的每次重新加载,实现热加载

  1. 本来重写 findLoadedClass()即可,直接返回null,实现热加载,但是ClassLoader把该方法设置为final了,不希望去破坏这个规则
  2. 所以直接通过重写过的findClass来实现,相当于在上面的基础上做修改,findClass的路径为当前路径,每次loadClass,直接调用findClass方法即可,虽然只能修改本地的class类
  3. 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系统加载-->自己加载-->父类加载
  1. 核心加载器
  • CommonClassLoader(默认父加载器为AppClassLoader)。加载一些tomcat/lib下面的jar包,servlet-api.jar、jsp-api.jar等
  • catalinaClassLoader(默认是空,有需要的话需要自己配置)
  • sharedClassLoader(默认是空,有需要的话需要自己配置)
  • WebAppClassLoader(tomcat下 WebRoot/应用程序/WEB-INF/lib 和 class包下的类
  1. 层级结构
  • tomcat下每个应用程序都有一个独立的类加载器,WebAppClassLoader,因为不同应用程序的类不能乱加载,更不能公用啊。
  • 但是有一些通用的类,比如JUnit、Log4j类,是不同应用程序公用的类,所以有sharedClassLoader出场,可以设置路径来实现这个类加载器,从而实现不同应用程序共同加载一些通用工具类
  • 再上一层就是CommonClassLoader:下面由shardClassLoader和CatalinaClassLoader,一个是所有应用程序,一个是tomcat容器的扩展类加载器
  • 再上一层就是AppClassLoader了,就是传统的模式往上走了
  1. 加载逻辑(源码 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类继承代理类,拦截相应的方法,实现代理

你可能感兴趣的:(JVM类加载机制)