类装载器

1. class装载验证流程

1.1. 加载 装载类的第一个阶段, 取得类的二进制流,转为方法区数据结构,在Java堆中生成对应的java.lang.Class对象
1.2. 链接
  • 1.2.1 验证 保证Class流的格式是正确的

    • 文件格式验证
      • 是否以0xCAFEBABE开头
      • 版本号是否合理
    • 1.2.2 元数据验证
      • 是否有父类
      • 继承了final类?
      • 非抽象类实现了所有的抽象方法
    • 1.2.3 字节码验证 (很复杂) 通过验证的class也不一定是没有问题的
      • 运行检查
      • 栈数据类型和操作码数据参数吻合
      • 跳转指令指定到合理的位置
    • 1.2.4 符号引用验证
      • 常量池中描述类是否存在
      • 访问的方法或字段是否存在且有足够的权限
  • 准备

    • 分配内存,并为类设置初始值 (方法区中)
      • public static int v=1;
      • 在准备阶段中,v会被设置为0
      • 在初始化的中才会被设置为1
      • 对于static final类型,在准备阶段就会被赋上正确的值(常量在准备时就会被赋上正确的值)
        public static final int v=1;
  • 解析

    • 符号引用 字符串引用对象不一定被加载替换为直接引用指针或者地址偏移量引用对象一定在内存
1.3. 初始化
  • 执行类构造器
    • static变量 赋值语句
    • static{}语句
  • 子类的调用前保证父类的被调用
  • 是线程安全的
  • 思考:Java.lang.NoSuchFieldError错误可能在什么阶段抛出

什么是类装载器

  • ClassLoader是一个抽象类
  • ClassLoader的实例将读入Java字节码将类装载到JVM中
  • ClassLoader可以定制,满足不同的字节码流获取方式
  • ClassLoader负责类装载过程中的加载阶段

JDK中ClassLoader默认设计模式

  • 重要方法
public Class loadClass(String name) throws ClassNotFoundException
载入并返回一个Class
protected final Class defineClass(byte[] b, int off, int len)
定义一个类,不公开调用
protected Class findClass(String name) throws ClassNotFoundException
loadClass回调该方法,自定义ClassLoader的推荐做法
protected final Class findLoadedClass(String name) 
寻找已经加载的类(找不到才要去加载类)
  • 分类
    BootStrap ClassLoader 启动类加载器
    Extension ClassLoader扩展类加载器
    App ClassLoader应用/系统类加载器
    Custom ClassLoader自定义类加载器
    启动类加载器没有parent,因为它是最顶层,其他每个ClassLoader都有一个Parent作为父亲
    参考
    自底向上:检查类是否已被加载
    自顶向下:尝试加载类
类装载器_第1张图片
类加载器关系.png

-Xbootclasspath 可以手动指定 boot classpath
类加载例子:
位于:D:\helloJVM\HelloClassLoader.java:

public class HelloClassLoader {
    public void print(){
        System.out.println("I am in bootloader");
    }
}

应用中的HelloClassLoader.java:

public class HelloClassLoader {
    public void print(){
        System.out.println("I am in apploader");
    }
}

及定位class是被哪个loader定位的应用程序:

public class LookForClassLoader {
  public static void main(String[] args) {
      HelloClassLoader loader=new HelloClassLoader();
      loader.print();
  }
  1. 直接执行main方法(即用app loader来加载 HelloClassLoader )


    类装载器_第2张图片
    loader.print()结果.png
  2. 添加参数-Xbootclasspath/a:D:\helloJVM,并编译HelloClassLoader.java。执行main方法

D:\>cd helloJVM
D:\helloJVM>javac HelloClassLoader.java
类装载器_第3张图片
要记得编译哦.png
loader.print()结果
  • I am in apploader 在classpath中却没有被加载,说明类的加载是从上往下
  1. 保持2的条件不变,强制在app 类加载器中加载classPath中的 HelloClassLoader
public class LookForClassLoader {
    public static void main(String[] args) {
        try {
            forceLoadInAppLoader();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void forceLoadInAppLoader() throws Exception {
        //  强制在apploader中加载 HelloClassLoader
        ClassLoader cl = LookForClassLoader.class.getClassLoader();
        byte[] bHelloClassLoader = loadClassBytes("jvmstudy.HelloClassLoader");
        Method md_defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
        md_defineClass.setAccessible(true);
        md_defineClass.invoke(cl, bHelloClassLoader, 0, bHelloClassLoader.length);
        md_defineClass.setAccessible(false);
        HelloClassLoader loader = new HelloClassLoader();
        System.out.println(loader.getClass().getClassLoader());
        loader.print();
    }


    private static byte[] loadClassBytes(String className) throws IOException {
        //获取class文件路径
        String classFile = getClassFile(className);
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(classFile);
        } catch (FileNotFoundException e) {
            System.out.println(e);
            return null;
        }
        byte[] bytes = new byte[fis.available()];
        fis.read(bytes);
        fis.close();
        return bytes;
    }

    private static String getClassFile(String name) {
        StringBuffer sb = new StringBuffer("E:\\myspace\\local-git-hub\\Steping\\src\\");
        name = name.replace('.', File.separatorChar) + ".class";
        sb.append(File.separatorChar + name);
        return sb.toString();
    }
}
类装载器_第4张图片
loader.print()结果.png
  • 在查找类的时候,先在底层的Loader查找,是从下往上的。app Loader能找到,就不会去上层加载器加载

即双亲委派模式,但是其问题也就是在于顶层classLoader 无法加载底层classLoader中的类

  • Java框架(rt.jar)如何加载应用的类? 在app Loader中包含一个rt接口的实例
    javax.xml.parsers包中定义了xml解析的类接口,Service Provider Interface SPI 位于rt.jar 。即接口在启动ClassLoader中,而SPI的实现类,在AppLoader。
Thread. setContextClassLoader() 即上下文加载器

是一个角色/职责,任何加载器都可以来承担这个角色,就像班长。
用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题
基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例

rt.jar 中javax.xml.parsers.FactoryFinder展示如何在启动类加载器加载AppLoader的类 ,即上下文ClassLoader可以突破双亲模式的局限性

/**
     * Attempt to load a class using the class loader supplied. If that fails
     * and fall back is enabled, the current (i.e. bootstrap) class loader is
     * tried.
     *
     * If the class loader supplied is null, first try using the
     * context class loader followed by the current (i.e. bootstrap) class
     * loader.
     *
     * Use bootstrap classLoader if cl = null and useBSClsLoader is true
     */
    static private Class getProviderClass(String className, ClassLoader cl,
            boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException
    {
        try {
            if (cl == null) {
                if (useBSClsLoader) {
                    return Class.forName(className, false, FactoryFinder.class.getClassLoader());
                } else {
                    cl = ss.getContextClassLoader();
                    if (cl == null) {
                        throw new ClassNotFoundException();
                    }
                    else {
                        return Class.forName(className, false, cl);
                    }
                }
            }
            else {
                return Class.forName(className, false, cl);
            }
        }
        catch (ClassNotFoundException e1) {
            if (doFallback) {
                // Use current class loader - should always be bootstrap CL
                return Class.forName(className, false, FactoryFinder.class.getClassLoader());
            }
            else {
                throw e1;
            }
        }
    }

打破常规模式

  • 双亲模式是默认的模式,但不是必须这么做
  • Tomcat的WebappClassLoader 就会先加载自己的Class,找不到再委托parent
  • OSGi的ClassLoader形成网状结构,根据需要自由加载Class
破坏双亲模式例子:

OrderClassLoaders

热替换`当一个class被替换后,系统无需重启,替换的类立即生效

`

很不错的Java类加载器(ClassLoader) 推荐

你可能感兴趣的:(类装载器)